iOS17 で tableView.tableHeaderView = searchController.searchBar してたらバグったので UISearchBar を使うことにした

iOS17 にしたら tableHeaderView に入れた searchController がバグって表示されるようになってしまった。

最初は hidesNavigationBarDuringPresentation を true にしたらいい感じにはなったんだけど、NavigationBar が隠れたまま次の画面に遷移すると NavigationBar が消えたままになってしまいダメだった。

解決するために試行錯誤したが、結局は UISearchController を使うのをやめて UISearchBar を使うことで解決した。

Before

final class ViewController: UIViewController {
    let searchController = UISearchController(searchResultsController: nil)

    override func viewDidLoad() {
        searchController.searchResultsUpdater = self
        searchController.obscuresBackgroundDuringPresentation = false
        searchController.hidesNavigationBarDuringPresentation = false
        searchController.searchBar.placeholder = "placeholder"
        searchController.delegate = self
        searchController.searchBar.delegate = self
        tableView.tableHeaderView = searchController.searchBar
    }

extension ViewController: UISearchResultsUpdating, UISearchControllerDelegate {
    func updateSearchResults(for searchController: UISearchController) {
        print(searchController.searchBar.text ?? "")
    }
}

extension ViewController: UISearchBarDelegate {
    func searchBar(_ searchBar: UISearchBar, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
        DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) { [weak self] in
            print(keyword)
        }

        return true
    }
}

After

final class ViewController: UIViewController {
    let searchBar = UISearchBar()

    override func viewDidLoad() {
        searchBar.delegate = self
        searchBar.placeholder = "placeholder"
        searchBar.frame = CGRect(x:0, y:0, width:self.view.frame.width, height:56)
        tableView.sectionHeaderTopPadding = 0
        tableView.tableHeaderView?.frame = searchBar.frame
        tableView.tableHeaderView = searchBar
    }

extension ViewController: UISearchBarDelegate {
    func searchBar(_ searchBar: UISearchBar, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
        DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) { [weak self] in
            print(searchBar.text!)
        }

        return true
    }
    
    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
        DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) { [weak self] in
            print(searchBar.text!)
        }
    }
    
    func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
        searchBar.resignFirstResponder()
    }

    func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
        searchBar.resignFirstResponder()
    }

    func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
        searchBar.setShowsCancelButton(true, animated: true)
    }

    func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
        searchBar.setShowsCancelButton(false, animated: true)
    }
}

補足

DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) はこれ

mob-wasao.hatenablog.jp

おわり

iOSはNavigationBarと検索バー周りはぶっ壊れがちなので大変。
iOS13 の時も苦しんでいた。