如何过滤UICollectionView并保持键盘?

时间:2013-03-06 18:34:58

标签: ios keyboard uicollectionview uisearchbar reloaddata

我已向UISearchBar添加了UICollectionView,并在代理searchBar:textDidChange:中过滤了我的模型并致电[collectionView reloadData]reloadData(以及reloadSection等)想要从搜索栏的文本字段中删除firstResponder,从而解除键盘。

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

有什么想法吗?

11 个答案:

答案 0 :(得分:9)

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

答案 1 :(得分:6)

我最近遇到了同样的问题,需要一段时间才能解决问题。 问题是当文本字段嵌套在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];
}

然后在我的View Controller中:

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

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

答案 2 :(得分:3)

如果您已将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];
}

答案 3 :(得分:2)

解决了它:

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

答案 4 :(得分:2)

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

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

这样就不需要像此问题列出的其他答案那样需要未定义的行为。

https://github.com/brennanMKE/PullDownSearch

答案 5 :(得分:1)

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

我有一堆收集视图单元格;

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()。见下文。

只需将 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()
}

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

希望有帮助!

答案 6 :(得分:0)

我认为您在搜索栏委托中调用了-resignFirstResponder方法,这会导致每次输入被解除的值时

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

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

如果您使用按钮进行搜索,则可以在那里添加键盘隐藏选项。但是使用此类选项时无法实现实时更新

答案 7 :(得分:0)

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

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

答案 8 :(得分:0)

以下是我管理它的方式。我的UISearchBar是我的集合视图中的标题补充视图。在搜索栏委托的文本更改时,我设置了一个标记,搜索是活动的(或不是)并重新加载了集合视图

- (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 :(得分:0)

我刚刚添加了

    [searchBar becomeFirstResponder];

之后

    [collectionView reloadData];

答案 10 :(得分:0)

这是因为在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)
}

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