24

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

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

有任何想法吗?

4

11 回答 11

10

在 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];
        }];
}
于 2015-03-17T10:58:43.783 回答
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];
}

然后在我的视图控制器中:

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

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

于 2013-12-06T13:11:59.090 回答
3

如果您已将UISearchBar作为标题添加到调用中,UICollectionViewreloadData调用将通过删除并重新添加UISearchBar导致它丢失的标题来“刷新”标题firstResponder

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

// If we've lost first-responder, restore it.
if ((wasFirstResponder) && (![self.searchBar isFirstResponder])) {
    [self.searchBar becomeFirstResponder];
}
于 2013-03-24T06:49:55.453 回答
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];
}
于 2013-11-14T23:27:15.900 回答
2

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

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

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

https://github.com/brennanMKE/PullDownSearch

于 2016-05-17T23:02:42.187 回答
1

我在处理 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()
}

而已。键盘隐藏后,您可以保存数据。

希望能帮助到你!

于 2019-10-17T12:56:22.403 回答
0

以下是我的管理方式。我将 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];
    }
}
于 2013-09-23T18:48:52.563 回答
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];
}

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

于 2013-03-06T19:30:54.263 回答
0

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

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

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

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

于 2013-03-06T19:12:33.100 回答
0

我刚刚添加

    [searchBar becomeFirstResponder];

打电话后

    [collectionView reloadData];
于 2013-10-21T15:40:17.203 回答
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)
}

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

于 2020-05-26T02:07:20.960 回答