I created a SearchController in tvOS, which shows the results in a CollectionView with horizontal scroll.
The final result looks quite horrible because the keyboard automatically dismisses when the focus moves from the keyboard to the results.
Because the height of the collection view is larger without the keyboard, the elements on it are automatically realigned in the collection view. That makes the UX very confusing.
You can see the problem in the following GIF. Moving down the focus from the keyboard to the results, ends up even focusing a non expected element. ("Seven" instead of "Five")
Is there any way to avoid that the keyboard collapses?. I noticed that when the results view contains a non scrollable view, the keyboard doesn't collapse, but I need to make my results scrollable.
Here you can find a code reproducing the issue.
import UIKit
private var items = ["One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten", "Eleven", "Twelve"]
final class MySearchViewController: UIViewController, UICollectionViewDataSource {
override func viewDidLoad() {
super.viewDidLoad()
setUpView()
}
// MARK: - Private
private lazy var searchContainerViewController: UISearchContainerViewController = {
return UISearchContainerViewController(searchController: searchController)
}()
private lazy var searchController: UISearchController = {
let searchController = UISearchController(searchResultsController: searchResultsController)
return searchController
}()
private lazy var searchResultsController: UIViewController = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
let searchResultsController = UICollectionViewController(collectionViewLayout: layout)
searchResultsController.collectionView.dataSource = self
MyCell.register(in: searchResultsController.collectionView)
return searchResultsController
}()
private func setUpView() {
embed(viewController: searchContainerViewController, inContainerView: view)
}
// MARK: - UICollectionViewDataSource
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return items.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MyCell.reuseIdentifier, for: indexPath) as! MyCell
cell.titleLabel.text = items[indexPath.row]
return cell
}
}
class MyCell: UICollectionViewCell {
static var reuseIdentifier: String { return String(describing: self) + "ReuseIdentifier" }
var titleLabel: UILabel!
public static func register(in collectionView: UICollectionView) {
collectionView.register(MyCell.self, forCellWithReuseIdentifier: MyCell.reuseIdentifier)
}
override init(frame: CGRect) {
super.init(frame: frame)
titleLabel = UILabel(frame: bounds)
backgroundColor = .blue
contentView.addSubview(titleLabel)
}
override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
backgroundColor = isFocused ? .red : .blue
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
A workaround to avoid focusing a wrong element could be to assign a fixed height to the CollectionView. But doing that, still hides the keyboard, leaving a lot of redundant empty space on the screen.
Since hiding the keyboard is useless in this situation. I would like to keep it always visible.
This is the result of using a fixed height for the collection view.
It was not really possible until tvOS 14, when Apple released new property searchControllerObservedScrollView
: https://developer.apple.com/documentation/uikit/uisearchcontroller/3584820-searchcontrollerobservedscrollvi
By setting your collectionView on this property, the SearchController will automatically adapt searchbar + keyboard position to your CollectionView offset.
You can adapt your code this way :
private lazy var searchController: UISearchController = {
let searchController = UISearchController(searchResultsController: searchResultsController)
if #available(tvOS 14.0, *) {
searchController.searchControllerObservedScrollView = (searchResultsController as? UICollectionViewController)?.collectionView
}
return searchController
}()
To get rid of nullable cast, just change searchResultsController
return type to UICollectionViewController
. It should not affect your implementation.