UITableViewController与UISearchDisplayController多选同步

时间:2014-08-22 20:31:06

标签: ios uitableview uisearchbar uisearchdisplaycontroller

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

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

tableView个对象都allowsMultipleSelection设置为YES

2 个答案:

答案 0 :(得分:2)

这项技术的基础来自Erica Sadun的非常有用的书" The Core iOS 6 Developer's Cookbook"第四版,由Addison Wesley出版。我从Erica的书中提出了许多想法和代码的解决方案。


备注

  • 这不优雅,但确实有效。
  • 此解决方案适用于运行iOS 7及更低版本的目标。 iOS 8中不推荐使用UISearchDisplayController,而是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);

使用父TVC中包含的managedObjectID方法设置属性arrayObjectsprepareForSegue。只设置了一个或另一个,具体取决于您是传递一个NSManagedObjectID(单个选择),还是NSArray多个NSManagedObject s(多个选择)。

可以删除属性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数据源方法UITableViewCell填充cellForRowAtIndexPath,如下所示...

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

希望您喜欢通过这个小小的享受!任何问题随时都可以问。

答案 1 :(得分:1)

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

UITableView时,任何UITableViewDataSource子类都需要调用tableView:cellForRowAtIndexPath: reloadData方法。这就是你做

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

最好在IMO中覆盖UITableViewCell setSelected:animated:方法,以便您可以更好地控制UITableViewDelegate方法tableView:didSelectRowAtIndexPath:

祝你好运,希望这有帮助〜