UITableViewController 与 UISearchDisplayController 多选同步 [英] UITableViewController with UISearchDisplayController multiple selection sync
问题描述
我正在尝试找出在 UITableViewController
和 UISearchDisplayController
之间保持行选择同步的最优雅方式.
I'm trying to figure out the most elegant way of keeping row selection in sync between a UITableViewController
and a UISearchDisplayController
.
当在 UITableViewController
中选择一行时,我希望在 UISearchDisplayController
为 active
时显示同一行,反之亦然.
When a row is selected in the UITableViewController
I want the same row to appear as selected when the UISearchDisplayController
is active
, and vice versa.
两个 tableView
对象都将 allowsMultipleSelection
设置为 YES
.
Both tableView
objects have allowsMultipleSelection
set to YES
.
推荐答案
此技术的基础来自 Erica Sadun 非常有用的书The Core iOS 6 Developer's Cookbook"第四版,该书由 Addison Wesley 出版.我根据 Erica 书中提出的想法和代码开发了许多解决方案.
The basis of this technique was gleaned from Erica Sadun's very helpful book "The Core iOS 6 Developer's Cookbook" fourth edition, published by Addison Wesley. I have developed many solutions from the ideas and code presented in Erica's book.
注意事项
- 这并不优雅,但确实有效.
- 此解决方案适用于运行 iOS 7 及更低版本的目标.
UISearchDisplayController
在 iOS 8 中被弃用,取而代之的是UISearchController
. - 为了尽量缩短答案,此解决方案省略了正确准备
self.tableView
和self.searchDisplayController.searchResultsTableView
表所需的代码块视图. - 此解决方案假定使用功能正常的 Core Data Stack 和
NSFetchedResultsController
.
- This is not elegant but it does work.
- This solution is for a target running iOS 7 and below.
UISearchDisplayController
is deprecated in iOS 8 in favour ofUISearchController
. - To attempt to keep the answer as short as possible, this solution leaves out chunks of code necessary to properly prepare both
self.tableView
andself.searchDisplayController.searchResultsTableView
table views. - This solution assumes use of functioning Core Data Stack and
NSFetchedResultsController
.
本质上,我们使用 NSMutableDictionary
来维护由 self.tableView
和 self.searchDisplayController.searchResultsTableView
使用的所选单元格的记录代码>.
Essentially we use an NSMutableDictionary
to maintain a record of selected cell/s that is used by both self.tableView
and self.searchDisplayController.searchResultsTableView
.
此技术可用于旨在注册和跟踪一个或多个选定单元格的表视图和控制器.
This technique can be used for table views and controllers that are intended to register and track one or more selected cells.
我可能会遗漏一些步骤,因为我实施此解决方案已经有一段时间了,所以请告诉我,我会检查它.
I may miss a few steps as it has been a while since I implemented this solution, so let me know and I will check it.
准备一组公共属性,包括...
Prepare a set of public properties, including...
@property (nonatomic, retain) NSMutableArray *searchResults;
@property (nonatomic, strong) NSManagedObjectID *managedObjectID;
@property (nonatomic, strong) NSArray *arrayObjects;
@property (nonatomic) BOOL isArray;
@property (nonatomic, strong) void (^blockSelectManagedObjectID)(NSManagedObjectID *objectID);
@property (nonatomic, strong) void (^blockSelectManagedObjects)(NSArray *objects);
属性managedObjectID
和arrayObjects
是使用父TVC 中包含的prepareForSegue
方法设置的.只设置一个或另一个,这取决于您传递的是一个 NSManagedObjectID
(单选),还是多个 NSManagedObject
的 NSArray
(多选).
The properties managedObjectID
and arrayObjects
are set using a prepareForSegue
method contained within the parent TVC. Only one or the other is set, depending on whether you are passing one NSManagedObjectID
(single selection), or an NSArray
of multiple NSManagedObject
s (multiple selections).
属性 isArray
可以删除,但我将其包含在内是为了便于编码和代码可读性.它还在上述父 TVC 中的相同 prepareForSegue
方法中设置.
The property isArray
could be removed, but I include it for my ease of coding and code readability. It is also set in the same prepareForSegue
method within the parent TVC mentioned above.
块在父 TVC 中定义并在退出该 TVC 时更新父 TVC 中的数据.
The blocks are defined in the parent TVC and update data in the parent TVC when exiting this TVC.
总而言之,除了 searchResults
之外,这些公共属性是由父 TVC 设置的.
To summarise, apart from searchResults
, these public properties are set by the parent TVC.
准备一组私有属性,包括...
Prepare a set of private properties, including...
@property (nonatomic, strong) NSMutableDictionary *dictionaryTableRowCheckedState; //our primary dictionary!
@property (nonatomic, strong) NSMutableArray *arrayObjectsSelected;
@property (nonatomic, strong) NSIndexPath *indexPathSelected;
@property (nonatomic, strong) NSIndexPath *indexPathObjectFromArray;
@property (nonatomic, strong) NSManagedObjectID *cellObjectID;
第三步
在 TVC 生命周期方法 viewWillAppear
中设置您的私有属性.
Step Three
Set your private properties in the TVC lifecycle method viewWillAppear
.
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self setDictionaryTableRowCheckedState:[NSMutableDictionary dictionary]];
[self setArrayObjectsSelected:[NSMutableArray arrayWithArray:self.arrayObjects]];
[self setIndexPathSelected:nil];
[self setIndexPathObjectFromArray:nil];
[self.tableView reloadData];
//<<_YOUR_OTHER_CODE_>>
}
第四步
准备使用 TVC 数据源方法 cellForRowAtIndexPath
填充您的 UITableViewCell
,如下...
Step Four
Prepare to populate your UITableViewCell
s using the TVC data source method cellForRowAtIndexPath
, as follows...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
//<<_YOUR_OTHER_CODE_>>
//<<including...>>
if (tableView == self.tableView) {
rowEntity = [self.fetchedResultsController objectAtIndexPath:indexPath];
} else {
rowEntity = [self.searchResults objectAtIndex:indexPath.row];
}
[self setCellObjectID:[rowEntity objectID]];
//<<_YOUR_OTHER_CODE_>>
[cell setAccessoryType:UITableViewCellAccessoryNone];
NSIndexPath *indexPathLastManagedObject = nil;
// If there exists 'checked' value/s, manage row checked state
if (self.managedObjectID || self.arrayObjects.count) {
BOOL isChecked = NO;
if (!self.isArray) {
if (self.cellObjectID == self.managedObjectID) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
isChecked = YES;
self.indexPathSelected = indexPath;
NSManagedObject *localManagedObject = nil;
if (tableView == self.tableView) {
localManagedObject = [self.fetchedResultsController objectAtIndexPath:indexPath];
} else {
localManagedObject = [self.searchResults objectAtIndex:indexPath.row];
}
indexPathLastManagedObject = indexPath;
self.managedObjectID = localManagedObject.objectID;
}
} else if (self.isArray) {
if (self.arrayObjectsSelected.count) {
for (NSManagedObject *localManagedObject in self.arrayObjectsSelected) {
if (self.cellObjectID == localManagedObject.objectID) {
isChecked = YES;
indexPathLastManagedObject = indexPath;
break;
}
}
}
self.dictionaryTableRowCheckedState[indexPath] = @(isChecked);
cell.accessoryType = isChecked ? UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone;
} else {
NSLog(@"%@ - %@ - has (possible undefined) E~R~R~O~R attempting to set UITableViewCellAccessory at indexPath: %@_", NSStringFromClass(self.class), NSStringFromSelector(_cmd), indexPath);
}
}
return cell;
}
第五步
当然,我们需要一个相当笨重的 TVC 委托方法 didSelectRowAtIndexPath
来处理用户对单元格的选择和取消选择.
Step Five
Of course we need a fairly chunky TVC delegate method didSelectRowAtIndexPath
to handle the selection and deselection of cells by the user.
请注意,我在我的代码中强调了块回调的使用,尽管没有详细提到这些 - 这是我选择更新父 TVC 中数据的方法.如果您希望我更新代码以合并块回调,请告诉我(这里已经有很多代码).
Notice that I highlight the use of block callbacks in my code, although these have not been mentioned in detail - it is my method of choice to update data in the parent TVCs. If you'd like me to update the code to incorporate block callbacks let me know (just a lot of code here already).
另请注意,当我们处于单选模式时,我会弹出 TVC.如果我们处于多小区选择模式,返回父TVC显然必须是手动的.
Notice also that I pop the TVC when we are in single selection mode. If we are in multiple cell selection mode, the return to the parent TVC must obviously be manual.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
if (!self.isArray) {
if (self.indexPathSelected) {
if ((indexPath.section == self.indexPathSelected.section)
&& (indexPath.row == self.indexPathSelected.row)) {
[cell setAccessoryType:UITableViewCellAccessoryNone];
[self setIndexPathSelected:nil];
} else {
NSIndexPath *oldIndexPath = self.indexPathSelected;
UITableViewCell *oldCell = [tableView cellForRowAtIndexPath:oldIndexPath];
[oldCell setAccessoryType:UITableViewCellAccessoryNone];
[cell setAccessoryType:UITableViewCellAccessoryCheckmark];
[self setIndexPathSelected:indexPath];
NSManagedObject *localManagedObject = nil;
if (tableView == self.tableView) {
localManagedObject = [self.fetchedResultsController objectAtIndexPath:indexPath];
} else {
localManagedObject = [self.searchResults objectAtIndex:indexPath.row];
}
NSManagedObjectID *localObjectID = localManagedObject.objectID;
[self blockSelectManagedObjectID](localObjectID); //block callback to update parent TVC
}
} else {
[cell setAccessoryType:UITableViewCellAccessoryCheckmark];
[self setIndexPathSelected:indexPath];
NSManagedObject *localManagedObject = nil;
if (tableView == self.tableView) {
localManagedObject = [self.fetchedResultsController objectAtIndexPath:indexPath];
} else {
localManagedObject = [self.searchResults objectAtIndex:indexPath.row];
}
NSManagedObjectID *localObjectID = localManagedObject.objectID;
[self blockSelectManagedObjectID](localObjectID); //block callback to update parent TVC
}
[tableView deselectRowAtIndexPath:indexPath animated:YES];
[self.navigationController popViewControllerAnimated:YES];
} else if (self.isArray) {
NSManagedObject *localManagedObject = nil;
if (tableView == self.tableView) {
localManagedObject = [self.fetchedResultsController objectAtIndexPath:indexPath];
} else {
localManagedObject = [self.searchResults objectAtIndex:indexPath.row];
}
// Toggle the cell checked state
__block BOOL isChecked = !((NSNumber *)self.dictionaryTableRowCheckedState[indexPath]).boolValue;
self.dictionaryTableRowCheckedState[indexPath] = @(isChecked);
[cell setAccessoryType:isChecked ? UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone];
if (isChecked) {
[self.arrayObjectsSelected addObject:localManagedObject];
} else {
[self.arrayObjectsSelected removeObject:localManagedObject];
}
[tableView deselectRowAtIndexPath:indexPath animated:YES];
} else {
NSLog(@"%@ - %@ - has (possible undefined) E~R~R~O~R at indexPath: %@_", NSStringFromClass(self.class), NSStringFromSelector(_cmd), indexPath);
}
}
第六步
TVC 生命周期方法 viewWillDisappear
中的这段代码更新父 TVC 中的数据,当它是正在返回的托管对象的 NSArray
时,或者在这种情况下单个管理对象 ID 行被简单地取消选择并且没有选择其他行(如果在进入表视图/TVC 时选择了该行(选中标记)).
Step Six
This code in the TVC lifecycle method viewWillDisappear
updates the data in the parent TVC when it is an NSArray
of managed objects that is being returned, or in the case that a single managed object ID row is simply deselected and no other row is selected (in the case that the row was selected (check marked) on entering the table view / TVC).
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
NSLog(@"%@ - %@ - values for:\n indexPathSelected: %@\n indexPathObjectFromArray: %@\n\n", NSStringFromClass(self.class), NSStringFromSelector(_cmd), self.indexPathSelected, self.indexPathObjectFromArray);
if (self.passBackManagedObjects) {
if (self.isArray) {
// Return an array of selected NSManagedObjects
[self blockSelectManagedObjects](self.arrayObjectsSelected);
} else if (self.indexPathSelected == nil) {
// Return nil where a previously selected (optional) entity is deactivated
[self blockSelectManagedObjectID](nil);
}
}
}
希望你喜欢这个小小的享受!如有任何问题,请随时提出.
Hope you enjoy working through this little treat! Any questions feel free to ask.
这篇关于UITableViewController 与 UISearchDisplayController 多选同步的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!