Как фильтровать UICollectionView и удерживать клавиатуру? - программирование
Подтвердить что ты не робот

Как фильтровать UICollectionView и удерживать клавиатуру?

Я добавил UISearchBar в UICollectionView, а в делегате searchBar:textDidChange: отфильтруйте мою модель и вызовет [collectionView reloadData]. reloadData (а также reloadSection и т.д.) хочет отнять firstResponder из текстового поля поиска, отбрасывая клавиатуру.

Я пытаюсь создать фильтр "живого обновления", и поэтому он раздражает, когда клавиатура исчезает после ввода каждого символа.

Любые идеи?

4b9b3361

Ответ 1

В функции делегата searchBar я использую executeBatchUpdates, во-первых, перезагружаю collectionView, затем вызываю [self.searchBar startFirstResponder] для отображения клавиатуры

- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar{
    [self setEditing:NO animated:YES];
    [searchBar setShowsCancelButton:YES animated:YES];

        [self.collectionView performBatchUpdates:^{
            [self.collectionView reloadData];
        } completion:^(BOOL finished) {
            [self.searchBar becomeFirstResponder];
        }];
}

Ответ 2

Я столкнулся с той же проблемой в последнее время, и потребовалось некоторое время, чтобы устранить ее. Проблема в том, что когда текстовое поле вложено в ReusableCollectionView, следующее не работает.

[self.collectionView reloadData];
[self.textField becomeFirstResponder];

Кроме того, для меня он отлично работал на симуляторе, но не работал на устройстве.

Настройка диспетчера представлений в качестве делегата текстового поля и реализация

- (BOOL)textFieldShouldEndEditing:(UITextField *)textField {
    return NO;
}

не работал, потому что представление коллекции результатов не обновлялось. Я догадываюсь, что до перезагрузки коллекции его взглядов пытается удалить фокус из всех вложенных элементов управления, но это невозможно - мы возвращаем NO из метода делегирования текстового поля.

Итак, решение для меня состояло в том, чтобы позволить системе удалить фокус из текстового поля, а затем вернуть его после перезагрузки. Вопрос в том, когда на самом деле это делать.

Во-первых, я попытался сделать это в

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath

но когда в коллекции не было элементов (что является нормальным случаем при фильтрации), этот метод не вызывался.

В конце концов я решил это следующим образом. Я создал подкласс UICollectionReusableView, реализующий два метода: -prepareForReuse и -drawRect:. Первый содержит вызов -setNeedsDesplay, который планирует drawRect вызывать следующий цикл рисования. Второй восстанавливает фокус, вызывая [self.textField статьFirstResponder], если для соответствующего флага установлено значение YES. Таким образом, идея заключалась в том, чтобы называть startFirstResponder "позже", но не слишком поздно, иначе происходит странное "прыжки" клавиатуры.

Мое пользовательское многоразовое представление коллекции выглядит следующим образом:

@interface MyCollectionReusableView : UICollectionReusableView

@property (nonatomic, assign) BOOL restoreFocus;
@property (nonatomic, strong) UITextField *textField;

@end


@implementation MyCollectionReusableView
@synthesize restoreFocus;
@synthesize textField;

- (void)setTextField:(UITextField *)value {
    textField = value;
    [self addSubview:textField];
}

- (void)drawRect:(CGRect)rect {
    [super drawRect:rect];
    if (self.restoreFocus) {
        [self.textField becomeFirstResponder];
    }
}

- (void)prepareForReuse {
    [self setNeedsDisplay];
}

Затем в моем контроллере просмотра:

- (void)viewDidLoad {
    [super viewDidLoad];

    [self.collectionView registerClass:[MyCollectionReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:HeaderReuseIdentifier];

    [self.textField addTarget:self action:@selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged];
}

- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
    if (UICollectionElementKindSectionHeader == kind) {
        MyCollectionReusableView *view = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader                                                                            withReuseIdentifier:HeaderReuseIdentifier                                                                                 forIndexPath:indexPath];

        //add textField to the reusable view
        if (nil == view.textField) {
            view.textField = self.textField;
        }
        //set restore focus flag to the reusable view
        view.restoreFocus = self.restoreFocus;
        self.restoreFocus = NO;
        return view;
    }
    return nil;
}

- (void)textFieldDidChange:(UITextField *)textField {
    self.restoreFocus = YES;
    [self.collectionView reloadData];
} 

Возможно, это не самое элегантное решение, но оно работает.:)

Ответ 3

Если вы добавили UISearchBar в качестве заголовка в UICollectionView, вызов reloadData "обновит" заголовок, удалив и повторно добавив UISearchBar, заставляя его потерять firstResponder.

Вы можете либо добавить свой UISearchBar другим способом, не используя заголовок, либо переопределить reloadData, проверьте, что в строке поиска в настоящее время находится первый ответчик, и если это произошло после вызова super, выполните:

// If we've lost first-responder, restore it.
if ((wasFirstResponder) && (![self.searchBar isFirstResponder])) {
    [self.searchBar becomeFirstResponder];
}

Ответ 4

Решил:

  UISearchBar *searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0.0, 0.0, 726.0f, 44.0f)];
  [_collectionView addSubview:searchBar];
  searchBar.delegate = self;
  [(UICollectionViewFlowLayout *)_collectionView.collectionViewLayout setSectionInset:UIEdgeInsetsMake(64, 20, 20, 20)];


- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
  [_collectionView reloadData];
  [searchBar becomeFirstResponder];
}

Ответ 5

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

Существующий экран, который я изменял, показывал указатель справа с буквами, чтобы перейти к каждому разделу, что означает, что существует много разделов, из-за которых сложнее узнать, как перезагрузить каждый из этих разделов без испорчения внутреннего состояния. Чтобы заставить его работать, когда текстовое поле становится первым ответчиком, организация просмотра коллекции меняет расположение всех элементов в один раздел, который перезагружается при поиске запускается. Теперь верхняя часть не перезагружается, поэтому она не теряет фокус.

Таким образом, поведение no undefined необходимо, как и другие ответы, перечисленные для этого вопроса.

https://github.com/brennanMKE/PullDownSearch

Ответ 6

Я столкнулся с этой проблемой при работе с UITextFields внутри UICollectionViewCell. Я думаю, что панель поиска может идти по тому же пути.

У меня есть несколько ячеек представления коллекции, которые заполняются;

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

    let question = questions[indexPath.row]
    let cell: QuestionBaseCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: cellName, for: indexPath) as! QuestionBaseCollectionViewCell
    cell.arrangeCell(question: question) // Here cell populates from server data 
    cell.delegate = self
    return cell
}

Этот QuestionBaseCollectionViewCell имеет метод делегата, который обновляет ответ на вопрос своему делегату. Мне нужно обновить данные на стороне сервера.

В методе делегата; Если я добавлю метод reloadData, текстовое поле уйдет в отставку и клавиатура исчезнет.

Также у меня нет прямого доступа к UITextField, который уступает, я не могу использовать cell.textField.becomeFirstResponder() и т.д.

Итак, я закомментирую метод reloadData().

func questionCollectionViewCell(_ cell: QuestionBaseCollectionViewCell, didChange choice: Choice) {
    guard let indexPath = collectionView.indexPath(for: cell) else { return }
    var question = questions[indexPath.row]
    guard let choiceIndex = question.choices?.firstIndex(where: { $0.id == choice.id }) else { return }
    question.choices?[choiceIndex] = choice
    questions[indexPath.row] = question

    // collectionView.reloadData()
}

Вот что я сделал;

Просто добавьте keyboardDidHideNotification к соответствующему контроллеру представления в методе viewWillAppear (примечание: вы можете поместить туда, куда хотите, зависит от ваших потребностей) следующим образом:

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    NotificationCenter.default.addObserver(self,
                                           selector: #selector(handleKeyboardDidHide(_ :)),
                                           name: UIResponder.keyboardDidHideNotification,
                                           object: nil)
}

и метод наблюдателя;

@objc func handleKeyboardDidHide(_ notification: Notification) {
    collectionView.reloadData()
}

Вот оно. После того, как клавиатура скроется, вы можете сохранить свои данные.

Надеюсь, это поможет!

Ответ 7

Я думаю, что вы вызываете метод -resignFirstResponder в делегате searchbar, который вызывает каждый раз, когда вы вводите значение, которое оно отклоняет.

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

Ну, я думаю, что лучше держать клавиатуру. Это правильный стиль пользовательского интерфейса после того, как вы закончили вводить текст для поиска, вы можете отменить его вручную. Я считаю, что это лучший вариант для фильтра обновления в реальном времени.

Если вы используете кнопку для поиска, вы можете включить опцию скрытия клавиатуры там. Но текущее обновление не может быть реализовано при использовании такой опции

Ответ 8

Я только что создал небольшое тестовое приложение. Следующий код не скрывает клавиатуру при вводе символов в панель поиска. Тем не менее, [UITableView reloadData] не уходит в отставку.

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    return arc4random() % 10;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {    
    UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"Cell" forIndexPath:indexPath];
    return cell;
}

- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
    [self.collectionView reloadData];
}

Вы уверены, что не откладываете его где-нибудь?

Ответ 9

Вот как мне это удалось. У меня был UISearchBar как дополнительный заголовок в моем представлении коллекции. В тексте didchange для делегата searchbar я устанавливаю флаг, что поиск активен (или нет) и перезагружает коллекцию

- (void)searchBar:(UISearchBar *)_searchBar textDidChange:(NSString *)searchText
{
    if([searchText isEqualToString:@""])
    {
        activeSearch = FALSE;
    }
    else
    {
        activeSearch = TRUE;
    }
    [self filterContentForSearchText:searchText scope:nil];
    [self.collectionView reloadData];
}

Затем в cellForItemAtIndexPath я проверяю, является ли клавиатура первым ответчиком, а поиск активен, если нет, я делаю это первым ответчиком:

 - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    ...

    if(activeSearch && ![searchBar isFirstResponder])
    {
        [searchBar becomeFirstResponder];
    }
}

Ответ 10

Я только что добавил

    [searchBar becomeFirstResponder];

после вызова

    [collectionView reloadData];