UITableViewController с синхронизацией множественного выбора UISearchDisplayController

Я пытаюсь найти самый элегантный способ синхронизации выбора строк между UITableViewController и UISearchDisplayController.

Когда строка выбрана в UITableViewController, я хочу, чтобы та же строка отображалась как выбранная, когда UISearchDisplayController равно active, и наоборот.

Оба объекта tableView имеют allowsMultipleSelection значение YES.


person Finbarr    schedule 22.08.2014    source источник


Ответы (2)


Основа этой методики была взята из очень полезной книги Эрики Садун «The Core iOS 6 Developer’s Cookbook», четвертое издание, опубликованной Addison Wesley. Я разработал множество решений на основе идей и кода, представленных в книге Эрики.


Примечания

  • Это не элегантно, но это работает.
  • Это решение предназначено для цели под управлением iOS 7 и ниже. UISearchDisplayController устарел в iOS 8 в пользу UISearchController.
  • Чтобы попытаться сделать ответ как можно короче, это решение не включает фрагменты кода, необходимые для правильной подготовки табличных представлений self.tableView и self.searchDisplayController.searchResultsTableView.
  • Это решение предполагает использование функционирующего Core Data Stack и NSFetchedResultsController.

По сути, мы используем NSMutableDictionary для ведения записи о выбранных ячейках, которые используются как self.tableView, так и self.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);

Свойства managedObjectID и arrayObjects устанавливаются с помощью метода prepareForSegue, содержащегося в родительском TVC. Устанавливается только одно или другое, в зависимости от того, передаете ли вы один NSManagedObjectID (одиночный выбор) или NSArray из нескольких NSManagedObject (множественный выбор).

Свойство isArray можно было бы удалить, но я включаю его для простоты кодирования и удобочитаемости кода. Он также устанавливается тем же методом prepareForSegue в родительском TVC, упомянутом выше.

Блоки определяются в родительском 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_>>
}

Шаг четвертый

Подготовьтесь к заполнению ваших UITableViewCell с помощью метода источника данных TVC 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);
    }
}

Шаг шестой

Этот код в методе viewWillDisappear жизненного цикла TVC обновляет данные в родительском TVC, когда возвращается NSArray управляемых объектов, или в случае, когда одна строка идентификатора управляемого объекта просто отменяется, а никакая другая строка не выбрана (в случай, когда строка была выбрана (отмечена галочкой) при входе в табличное представление / 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);
        }
    }
}

Надеюсь, вам понравится работать над этим маленьким удовольствием! Любые вопросы, не стесняйтесь спрашивать.

person andrewbuilder    schedule 26.08.2014

Вы используете один и тот же источник данных, поэтому технически вы не синхронизируетесь. Используйте один набор объектов для проверки.

Любой подкласс 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];
}

Лучше также переопределить метод UITableViewCell setSelected:animated: IMO, чтобы вы могли лучше контролировать UITableViewDelegate метод tableView:didSelectRowAtIndexPath:.

Удачи, надеюсь поможет~

person JackyJohnson    schedule 26.08.2014