UITableViewController 与 UISearchDisplayController 多选同步 [英] UITableViewController with UISearchDisplayController multiple selection sync

查看:19
本文介绍了UITableViewController 与 UISearchDisplayController 多选同步的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试找出在 UITableViewControllerUISearchDisplayController 之间保持行选择同步的最优雅方式.

I'm trying to figure out the most elegant way of keeping row selection in sync between a UITableViewController and a UISearchDisplayController.

当在 UITableViewController 中选择一行时,我希望在 UISearchDisplayControlleractive 时显示同一行,反之亦然.

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.tableViewself.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 of UISearchController.
  • To attempt to keep the answer as short as possible, this solution leaves out chunks of code necessary to properly prepare both self.tableView and self.searchDisplayController.searchResultsTableView table views.
  • This solution assumes use of functioning Core Data Stack and NSFetchedResultsController.

本质上,我们使用 NSMutableDictionary 来维护由 self.tableViewself.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);

属性managedObjectIDarrayObjects 是使用父TVC 中包含的prepareForSegue 方法设置的.只设置一个或另一个,这取决于您传递的是一个 NSManagedObjectID(单选),还是多个 NSManagedObjectNSArray(多选).

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 NSManagedObjects (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 UITableViewCells 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆