【问题标题】:UITableViewController with UISearchDisplayController multiple selection syncUITableViewController 与 UISearchDisplayController 多选同步
【发布时间】:2014-08-26 14:28:14
【问题描述】:

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

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

两个tableView 对象都将allowsMultipleSelection 设置为YES

【问题讨论】:

    标签: ios uitableview uisearchbar uisearchdisplaycontroller


    【解决方案1】:

    这项技术的基础是从埃里卡·萨顿 (Erica Sadun) 非常有用的书“The Core iOS 6 Developer's Cookbook”第四版中收集的,该书由 Addison Wesley 出版。我根据 Erica 书中提出的想法和代码开发了许多解决方案。


    注意事项

    • 这并不优雅,但确实有效。
    • 此解决方案适用于运行 iOS 7 及更低版本的目标。 UISearchDisplayController 在 iOS 8 中被弃用,取而代之的是 UISearchController
    • 为了尽量缩短答案,此解决方案省略了正确准备 self.tableViewself.searchDisplayController.searchResultsTableView 表视图所需的代码块。
    • 此解决方案假定使用正常的核心数据堆栈和NSFetchedResultsController

    基本上我们使用NSMutableDictionary 来维护self.tableViewself.searchDisplayController.searchResultsTableView 都使用的选定单元格的记录。

    此技术可用于旨在注册和跟踪一个或多个选定单元格的表视图和控制器。

    我可能会错过一些步骤,因为我实施此解决方案已经有一段时间了,所以请告诉我,我会检查一下。

    第一步

    准备一组公共属性,包括...

    @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(单选)还是多个NSManagedObjects 中的一个NSArray(多选)。

    属性isArray 可以删除,但为了便于编码和代码可读性,我将其包含在内。它也是在上面提到的父 TVC 中的同一 prepareForSegue 方法中设置的。

    块在父 TVC 中定义,并在退出此 TVC 时更新父 TVC 中的数据。

    总结一下,除了searchResults,这些公共属性都是由父TVC设置的。

    第二步

    准备一组私有属性,包括...

    @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 中设置您的私有属性。

    - (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填充你的UITableViewCells,如下...

    - (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 来处理用户对单元格的选择和取消选择。

    请注意,我在我的代码中强调了块回调的使用,尽管这些没有被详细提及 - 这是我在父 TVC 中更新数据的选择方法。如果您希望我更新代码以合并块回调,请告诉我(这里已经有很多代码了)。

    还请注意,当我们处于单选模式时,我会弹出 TVC。如果我们处于多单元格选择模式,返回父 TVC 显然必须是手动的。

    - (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 时选择了该行(选中标记))。

    - (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);
            }
        }
    }
    

    希望您喜欢这个小礼物!有任何问题欢迎随时提问。

    【讨论】:

      【解决方案2】:

      您使用相同的数据源,因此从技术上讲您不会同步。使用一组对象进行检查。

      任何UITableView子类在reloadData时都需要调用UITableViewDataSourcetableView:cellForRowAtIndexPath:方法。这就是你做类似的事情的地方

      if (![selectionSet containsObject:object]) { 
          [tableView selectRowAtIndexPath:indexPath animated:NO];
          [cell setSelected:YES animated:NO];
      }
      else {
          [tableView deselectRowAtIndexPath:indexPath];
          [cell setSelected:NO animated:NO];
      }
      

      最好也覆盖UITableViewCell setSelected:animated: 方法,这样您就可以更好地控制UITableViewDelegate 方法tableView:didSelectRowAtIndexPath:

      祝你好运,希望对你有帮助~

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-12-26
        • 2012-12-21
        • 2013-08-21
        • 1970-01-01
        • 2021-01-13
        • 1970-01-01
        相关资源
        最近更新 更多