Ios 如何过滤UICollectionView并保持键盘正常工作?

Ios 如何过滤UICollectionView并保持键盘正常工作?,ios,keyboard,uicollectionview,uisearchbar,reloaddata,Ios,Keyboard,Uicollectionview,Uisearchbar,Reloaddata,我已将UISearchBar添加到UICollectionView中,并在代理searchBar:textDidChange:筛选我的模型并调用[collectionView reloadData]reloadData(以及reloadSection等)希望从搜索栏的文本字段中删除firstResponder,从而取消键盘功能 我正在尝试构建一个“实时更新”过滤器,因此在输入每个字符后键盘都会消失是很烦人的 有什么想法吗?我想您正在调用searchbar委托中的-resignFirstRespo

我已将
UISearchBar
添加到
UICollectionView
中,并在代理
searchBar:textDidChange:
筛选我的模型并调用
[collectionView reloadData]
reloadData
(以及reloadSection等)希望从搜索栏的文本字段中删除firstResponder,从而取消键盘功能

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


有什么想法吗?

我想您正在调用searchbar委托中的
-resignFirstResponder
方法,这会导致每次输入值时,它都被取消

关于数据源方法和重新加载数据,您所处的路径是正确的。使用一个数组存储所有值—另一个用于加载collectionview的数组以及在搜索栏筛选器上输入的每个字母—并加载数据源数组,然后重新加载

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


如果您使用按钮进行搜索,则可以在其中包含键盘隐藏选项。但是,使用此选项时无法实现实时更新。我刚刚创建了一个小型测试应用程序。在搜索栏中输入字符时,以下代码不会隐藏键盘。也就是说,[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];
}

你确定,你没有在某个地方辞职吗?

如果你已经将
UISearchBar
作为标题添加到
UICollectionView
中,则
reloadData
调用将通过删除并重新添加
UISearchBar
来“刷新”标题,从而使其丢失
第一响应者

您可以以另一种方式添加
ui搜索栏
,不使用标题,也可以覆盖
reloadData
,检查搜索栏当前是否为第一响应者,如果是,则在调用
super
后执行以下操作:

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

这是我如何做到的。我将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中,我检查键盘是否为first responder,搜索是否处于活动状态,如果不是,我将其设为first responder:

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

    if(activeSearch && ![searchBar isFirstResponder])
    {
        [searchBar becomeFirstResponder];
    }
}
我刚才补充说

    [searchBar becomeFirstResponder];
打完电话

    [collectionView reloadData];
解决它:

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

我最近遇到了同样的问题,花了一段时间才修好。 问题是,当文本字段嵌套在ReusableCollectionView中时,以下内容不起作用

[self.collectionView reloadData];
[self.textField becomeFirstResponder];
此外,对我来说,它在模拟器上运行良好,但在设备上不起作用

将视图控制器设置为文本字段委托并实现

- (BOOL)textFieldShouldEndEditing:(UITextField *)textField {
    return NO;
}
无法工作,因为结果集合视图未刷新。我的猜测是-在重新加载其视图集合之前,它试图从所有嵌套控件中移除焦点,但它不能-我们从文本字段委托方法返回否

因此,我的解决方案是让系统从文本字段中移除焦点,然后在重新加载后将其取回。问题是什么时候真正做到这一点

首先,我试着在家里做这件事

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
但当集合中没有项时(这在筛选过程中是正常情况),就不会调用此方法

最终我用下面的方法解决了这个问题。 我创建了UICollectionReusableView子类,实现了两个方法:-prepareForReuse和-drawRect:。第一个包含-setNeedsDesplay调用,该调用在下一个绘图周期调度drawRect调用。如果相应的标志设置为“是”,则第二个将通过调用[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];
} 

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

在searchBar委托函数中,我使用PerformBatChUpdate,首先重新加载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];
        }];
}

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

我正在修改的现有屏幕在右侧显示了索引,其中包含导航到每个部分的字母,这意味着有许多部分,这使得我们更难知道如何在不破坏内部状态的情况下重新加载这些部分。为了在文本字段成为第一个响应者时工作,集合视图的组织将更改为将所有项目放置到一个单独的部分中,该部分将在搜索时重新加载 他跑了。现在顶部部分不会重新加载,因此不会失去焦点

这样,就不需要像这个问题列出的其他答案那样有任何未定义的行为


我在处理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()
}
以下是我所做的
键盘关闭后,我打电话给
重新加载数据。见下文。

只需在视图中的相应视图控制器中添加一个键盘DidHidenotification
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()
}
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
}
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)
}