【问题标题】:How to filter UICollectionView and keep keyboard up?如何过滤 UICollectionView 并保持键盘正常运行?
【发布时间】:2013-02-21 17:04:45
【问题描述】:

我已将UISearchBar 添加到UICollectionView 并在委托searchBar:textDidChange: 中过滤我的模型并调用[collectionView reloadData]reloadData(以及 reloadSection 等)想要从搜索栏的文本字段中删除 firstResponder,从而关闭键盘。

我正在尝试构建一个“实时更新”过滤器,因此在键入每个字符后让键盘消失很烦人。

有什么想法吗?

【问题讨论】:

  • 感谢您提出这个问题。

标签: ios keyboard uicollectionview uisearchbar reloaddata


【解决方案1】:

这是由于在 UISearchBarDelegate 方法内部调用了 reloadData()。解决此问题的一种方法是为包含搜索栏的标题视图专门设置一个额外的空白部分。然后代替 reloadData() 调用:

self.collectionView.reloadSections([1])

如果您没有多个数据部分,您可以通过加载虚假标题来消除第二个标题。

override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {

    if indexPath.section == 1 {
        let fakeView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "fakeHeader", for: indexPath)
        return fakeView
    }

    let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: searchHeader, for: indexPath) as! SearchHeader
    headerView.initializeHeader(with: self)

    return headerView
}

接下来您可以将仿标题的高度设置为 0

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {

    return section == 0 ? CGSize(width: collectionView.frame.width, height: 260) : CGSize(width: collectionView.frame.width, height: 0)
}

您将遇到的下一个问题将是单元格和第一个标题之间的间隙,这是由边缘插入引起的。所以消除第一部分的顶部和底部插图。

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
    if section == 1 {
        return UIEdgeInsets(top: 20, left: 20, bottom: 30, right: 20)
    }
    return UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20)
}

希望这可以节省一些时间。

【讨论】:

  • 您可能会面临另一个问题 - reloadSections 的性能远低于 reloadData
【解决方案2】:

我在处理 UICollectionViewCell 中的 UITextFields 时遇到了这个问题。 我认为搜索栏可能会遵循相同的路径。

我有一堆从中填充的集合视图单元格;

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

这就是我所做的;
我在键盘关闭后拨打了reloadData() 的电话。见下文。

只需在 viewWillAppear 方法中添加一个 keyboardDidHideNotification 到相应的视图控制器(注意:您可以根据需要放在任何位置)如下:

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

就是这样。键盘隐藏后,您可以保存数据。

希望对你有帮助!

【讨论】:

    【解决方案3】:

    答案是不要重新加载具有文本字段的部分。我通过将所有项目放在一个搜索中解决了这个问题,因此可以重新加载该单个部分。

    我正在修改的现有屏幕在右侧显示带有导航到每个部分的字母的索引,这意味着有很多部分使得更难知道如何重新加载每个部分而不弄乱内部状态。为了使它在文本字段成为第一响应者时工作,集合视图的组织会更改为将所有项目放入单个部分中,该部分会在搜索时重新加载 正在运行。现在顶部没有重新加载,所以它不会失去焦点。

    这种方式不需要像为这个问题列出的其他答案那样未定义的行为。

    https://github.com/brennanMKE/PullDownSearch

    【讨论】:

      【解决方案4】:

      在searchBar委托函数中,我使用performBatchUpdates,首先,重新加载collectionView,然后调用[self.searchBar becomeFirstResponder]来显示键盘

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

      【讨论】:

      • 其实这个答案是最完美的:stackoverflow.com/a/25796944/501439
      • 一定要看看@GeneCode 的评论。那里的答案实际上可以防止重新加载搜索栏部分,从而无需重新分配第一响应者状态。
      【解决方案5】:

      我最近遇到了同样的问题,需要一段时间才能解决。 问题是当文本字段嵌套在 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。如果相应的标志设置为 YES,则第二个通过调用 [self.textField becomeFirstResponder] 来恢复焦点。 所以想法是“稍后”调用 becomeFirstResponder ,但不要太晚,否则会发生奇怪的键盘“跳跃”。

      我的自定义可重用集合视图如下所示:

      @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];
      } 
      

      可能不是最优雅的解决方案,但它确实有效。 :)

      【讨论】:

      • 谢谢,我去看看。
      【解决方案6】:

      解决了:

        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];
      }
      

      【讨论】:

        【解决方案7】:

        我刚刚加了

            [searchBar becomeFirstResponder];
        

        调用后

            [collectionView reloadData];
        

        【讨论】:

          【解决方案8】:

          这就是我的管理方式。我将 UISearchBar 作为集合视图中的标题补充视图。在搜索栏委托的文本 didchange 上,我设置了一个标志,表明搜索处于活动状态(或不处于活动状态)并重新加载了 collectionview

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

          【讨论】:

            【解决方案9】:

            如果您已将UISearchBar 作为标头添加到UICollectionView,则reloadData 调用将通过删除并重新添加UISearchBar 来“刷新”标头,导致其丢失firstResponder

            你可以用另一种方式添加你的UISearchBar,不使用标题,或者覆盖reloadData,检查搜索栏当前是否是firstResponder,如果是,在调用super之后,执行:

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

            【讨论】:

            • 很好的解释。但我想知道为什么 TableView 不会遇到这个问题。
            • 你在哪里添加这个代码,什么是'wasFirstResponder'
            【解决方案10】:

            我认为您在搜索栏委托中调用 -resignFirstResponder 方法,这导致每次输入值时它都会被关闭

            关于数据源方法并重新加载数据,您处于正确的路径。使用一个数组存储所有值另一个数组用于加载集合视图和在搜索栏过滤器上输入的每个字母并加载数据源数组,然后重新加载

            嗯,我认为最好保留键盘。这是正确的 UI 样式,在您完成输入文本进行搜索后,您可以手动关闭它。我认为这是实时更新过滤器的更好选择。

            如果您使用按钮进行搜索,则可以在其中包含键盘隐藏选项。但使用此类选项时无法实现实时更新

            【讨论】:

              【解决方案11】:

              我刚刚创建了一个小型测试应用程序。以下代码在搜索栏中输入字符时不会隐藏键盘。也就是说,[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];
              }
              

              你确定,你不会在某个地方辞职吗?

              【讨论】:

                猜你喜欢
                • 2015-07-24
                • 2012-02-03
                • 1970-01-01
                • 2011-11-29
                • 1970-01-01
                • 2015-04-23
                • 1970-01-01
                • 2016-01-21
                • 2015-10-19
                相关资源
                最近更新 更多