Can't use UISearchController with UICollection

2019-03-09 05:13发布

问题:

In the WWDC 2014 talk " A Look Inside Presentation Controllers" the presenters showed how to setup a UISearchController in a UITableView. They do this by setting the searchController's searchBar's frame, then setting it as the tableView's tableHeaderView. Unfortunately, there isn't an equivalent of tableHeaderView for UICollectionView. With UISearchDisplayController, this would be simple: create a UISearchBar and add it to a custom UICollectionView section header, then initialize the UISearchDisplayController with the search bar. The problem is, you can't initialize a UISearchController with a UISearchBar, or even set the searchBar because it's a read-only property. I guess my real question is, what are my options? Is there a "good" way to implement search without UISearchDisplayController or UISearchController?

回答1:

With UISearchDisplayController, this would be simple: create a UISearchBar and add it to a custom UICollectionView section header, then initialize the UISearchDisplayController with the search bar

The search bar in a UISearchController is created for you. When you are asked the supplementary view in the data source method

- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath

add the searchController.searchBar as a subview of the supplementary view. Don't forget to call

[searchController.searchBar sizeToFit]

in order to give the search bar the appropriate size.



回答2:

My first question: Why can't you flip your logic and instead of creating a search bar, create a UISearchController first, grab its search bar and add it to the section header?

Second, collection views layouts aren't as simple as table views. It's hard for search to guess at your intentions with collection view layouts whereas a table view is pretty straightforward. So in the case of a collection view, you are free to add a search bar to the view but animating it to an active state will require some work. (Subclass UISearchController and return your own animation controller, then do whatever animation you want. Or just implement the methods from UIViewControllerAnimatedTransitioning on your subclass and don't call super, both should work)

Something you might want to try though is having the search bar appear from off screen. This is a built-in animation UISearchController supports when setActive: is called and the search bar isn't anywhere in the view hierarchy. Calendar does this... it's pretty cool. Instead of having a giant search bar always present, you can reduce search to an icon that drives presentation.

Finally, there are bound to be bugs. Please file bugs when you can't get things to work that you think should. I know, it's a broken record, but it really is a necessity.



回答3:

The answer accepted here seems outdated and not keeping several things in mind while answered. Those are as following:

  1. First As the implementation is not given, someone may guess that we should return the UISearchController.searchbar view as supplementary view, but you can't because there is a type mismatch.
  2. Second You add the UISearchController.searchbar in your dequeued supplementary header view as subview, but wait, you have to remove this view from other supplementary views, because this supplementary view may be re-used in other sections (it is pretty much common that you use multiple section in your collectionView)
  3. Thirdly This one is from user experience perspective. Say you have only one section, which containing the searchbar from the search controller. Now user scrolls down to the very end of the CollectionView, this top section will be gone far top of the screen, so in order to search, user have to scroll up all the way to beginning to search.

Recently i was working on to achieve such a user experience to search on a collectionView which has several section. Here is my solution.

In Storyboard, Use UIViewController instead of using UICollectionViewController. In the ViewController i add one UIView & one UICollectionView like following

---------ViewController----------
|                               |
|            UIView             |
|                               |
|===============================|
|                               |
|                               |
|                               |
|                               |
|                               |
|       UICollectionView        |
|                               |
|                               |
|                               |
|                               |
|                               |
|                               |
---------------------------------

The top UIView has a height constraint and pinned to top, left & right. Bottom CollectionView is pinned to bottom, left & right and vertical distance between top view and bottom collection view is set to zero. The top view is subclassed as FillSubView which is as follows

FillSubView.swift

import UIKit

class FillSubView: UIView {
    override func didAddSubview(_ subview: UIView) {
        subview.frame = self.bounds
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        subviews.first?.frame = self.bounds
    }
}

What it does, every time a subview added or the layout of the FillSubView changes, the first subview is resized to that content. You may find why i don't use constraint to pin the subview. My intention is to add UISearchController.searchbar as a subview in FillSubView. While the UISearchController get active and deactivated this searchbar is removed and re-added to the previous parent view. In that case if you strictly wanted to pin the searchbar to FillSubView you could override

- (void)didAddSubview:(UIView *)subview;

pin the subview (searchbar) to the parent view (top,bottom,left,right). While removing from superview constraints will be auto removed.

Now say the view controller is subclassed as MyViewController. I took an IBOutlet of top UIView, CollectionView and the top view height constraint as following

@IBOutlet weak var searchBarContainer: UIView! // top view
@IBOutlet weak var collectionView: UICollectionView! // bottom collection view
@IBOutlet weak var searchContainerHeightConstraint: NSLayoutConstraint! // top view height constraint

And in the MyViewController i added a function to setup the searchController as follows

private func setupSearchController() {

    searchController = UISearchController.init(searchResultsController: nil)
    searchController?.searchResultsUpdater = self
    searchController?.dimsBackgroundDuringPresentation = false
    searchController?.searchBar.placeholder = "Search"

    if let searchbar = searchController?.searchBar {
        searchBarContainer.addSubview(searchbar)
        searchContainerHeightConstraint.constant = searchbar.bounds.height
    }
    definesPresentationContext = true
}

i call this method from my ViewDidLoad overloaded method of ViewController. Here the whole point is the FillSubView class, which filled the searchbar along the whole view.

Delegate implementation of searchResultUpdater pretty common just filtering out the content which i wanted to show while the user typed in any text in searchbar.

This way i achieved my goal and it also conform to the 3 problem described earlier.

Hope it helps.