UISearchController的用法

簡介

UISearchController是iOS 8新增的一個類卤唉,用于實現(xiàn)搜索欄的默認行為啦逆,這也是官方推薦的搜索欄的實現(xiàn)方法。

行為

UITableViewController上長這個樣子

  • 初始狀態(tài)下搜索欄是隱藏的


  • 往下拉時安皱,搜索欄顯示并占據導航欄的位置


  • 點擊搜索欄進入搜索狀態(tài)


  • 輸入時進行實時搜索


用法1:使用當前View Controller顯示搜索結果

(1) 建立工程诗茎,Storyboard如下:


(2) 在MainViewController中設置搜索欄

class MainViewController: UITableViewController {

    private let items = [
        "Jon Snow",
        "Bran",
        "Theon",
        "Tarly",
        "Tyrion",
        "Tywin",
        "Jaime",
        "Cersei",
        "Ned",
        "Robb",
    ]

    private var searchResults = [String]()

    override func viewDidLoad() {
        super.viewDidLoad()

        let searchController = UISearchController(searchResultsController: nil)
        searchController.obscuresBackgroundDuringPresentation = false
        searchController.searchBar.placeholder = "Search name..."
        searchController.searchBar.autocapitalizationType = .none
        searchController.searchResultsUpdater = self
        navigationItem.searchController = searchController
    }
}
  • itemssearchResults來分別保存全部數據和搜索結果尿背。默認狀態(tài)下顯示全部數據默赂,搜索狀態(tài)下顯示搜索結果捻激。
  • 創(chuàng)建一個UISearchController實例钧排。參數searchResultsControllernil即表示沒有單獨的顯示搜索結果的界面敦腔,也就是使用當前界面來顯示。
  • obscuresBackgroundDuringPresentation表示搜索欄進入搜索狀態(tài)時恨溜,要不要在當前界面上顯示一層半透明的遮罩(效果可以在手機的設置界面上感受一下符衔,或者參考上面的第3張圖)找前。該屬性的默認值為true,但是如果沒有要把當前界面作為搜索結果顯示界面判族,最好設成false躺盛。否則,點擊搜索結果時就會點在遮罩上形帮,造成搜索欄退出搜索狀態(tài)槽惫,重新顯示全部數據。
  • searchBar進行一些外觀上的設置
  • 設置searchResultsUpdater以監(jiān)聽搜索事件辩撑。如果不需要實時搜索界斜,則不需要這一步。
  • 最后把實例放到navigationItem上合冀。

現(xiàn)在運行代碼各薇,下拉可以顯示搜索欄,只是還沒有實現(xiàn)搜索功能水慨。

(3) 根據狀態(tài)顯示數據

// MARK: - helpers
extension MainViewController {
    var isSearching: Bool {
        return navigationItem.searchController?.isActive ?? false
    }

    func item(at indexPath: IndexPath) -> String {
        return isSearching ? searchResults[indexPath.row] : items[indexPath.row]
    }

    var itemCount: Int {
        return isSearching ? searchResults.count : items.count
    }
}

// MARK: - UITableViewDataSource
extension MainViewController {
    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return itemCount
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        cell.textLabel?.text = item(at: indexPath)
        return cell
    }
}
  • isActive屬性來判斷當前是否在搜索狀態(tài)得糜。只有當搜索欄顯示并獲得焦點時,isActive才為true晰洒。
  • 根據狀態(tài)切換顯示內容

(4) 監(jiān)聽搜索事件
添加search方法朝抖,更新searchResults并刷新界面。

// MARK: - helpers
extension MainViewController {
    ...
    func search(_ text: String?) {
        if let text = text, !text.isEmpty {
            searchResults = items.flatMap { $0.range(of: text, options: .caseInsensitive) == nil ? nil : $0 }
        }
        else {
            searchResults = items
        }
        tableView.reloadData()
    }
}

實現(xiàn)UISearchResultsUpdating協(xié)議

// MARK: - UISearchResultsUpdating
extension MainViewController : UISearchResultsUpdating {
    func updateSearchResults(for searchController: UISearchController) {
        search(searchController.searchBar.text)
    }
}

運行代碼谍珊,就可以在搜索欄進行實時搜索了治宣。

(5) 如果不想用實時搜索,而只在輸入完成并按return時搜索砌滞。就把這行:

searchController.searchResultsUpdater = self

改成

searchController.searchBar.delegate = self
searchController.delegate = self

并實現(xiàn)這兩個協(xié)議

// MARK: - UISearchBarDelegate
extension MainViewController : UISearchBarDelegate {
    func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
        search(searchBar.text)
    }
}

// MARK: - UISearchControllerDelegate
extension MainViewController : UISearchControllerDelegate {
    func didDismissSearchController(_ searchController: UISearchController) {
        tableView.reloadData()
    }
}
  • 實現(xiàn)didDismissSearchController方法是為了在點擊Cancel按鈕退出搜索狀態(tài)時可以重新顯示所有數據侮邀。其實UISearchBarDelegate也有個searchBarCancelButtonClicked方法可以監(jiān)聽Cancel按鈕事件,但這個事件是發(fā)生在Cancel按鈕被點擊之時贝润,而不是在那之后绊茧。也就是當時searchControllerisActive屬性還是true,此時界面刷新也仍會顯示searchResults的內容打掘。

用法2:使用其它View Controller顯示搜索結果

多數時候华畏,我們都需要定制搜索界面(參考微信的搜索界面)。
(1) 以上面的代碼為基礎尊蚁,在Storyboard中新增一個UITableViewController亡笑,類名為SearchResultsViewController

class SearchResultsViewController: UITableViewController {

    private var results = [NSAttributedString]() {
        didSet {
            tableView.reloadData()
        }
    }

    func search(_ text: String, in items: [String]) {
        results = items.flatMap { item in
            let matchedRange = (item as NSString).range(of: text, options: .caseInsensitive)
            guard matchedRange.location != NSNotFound else {
                return nil
            }
            return highlighted(string: item, in: matchedRange)
        }
    }

    private func highlighted(string s: String, in range: NSRange) -> NSAttributedString {
        let s = NSMutableAttributedString(string: s)
        s.beginEditing()
        let highlightAttributes: [NSAttributedStringKey : Any] = [
            .foregroundColor : UIColor.blue,
            .font : UIFont.boldSystemFont(ofSize: 17),
        ]
        s.addAttributes(highlightAttributes, range: range)
        s.endEditing()
        return s
    }
}

extension SearchResultsViewController {
    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return results.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        cell.textLabel?.attributedText = results[indexPath.row]
        return cell
    }
}
  • search方法跟前面差不多,只是多了一步横朋,把匹配的部分高亮顯示仑乌。為此,要使用NSAttributedString數組來保存搜索結果。

接下來可以把MainViewController里搜索相關的代碼都去掉晰甚,變成:

class MainViewController: UITableViewController {

    private let items = ...

    override func viewDidLoad() {
        super.viewDidLoad()

        let searchResultsController = storyboard?.instantiateViewController(withIdentifier: "searchResults")
        let searchController = UISearchController(searchResultsController: searchResultsController)
        searchController.searchBar.placeholder = "Search name..."
        searchController.searchBar.autocapitalizationType = .none
        searchController.searchBar.delegate = self
        searchController.delegate = self
        navigationItem.searchController = searchController
        definesPresentationContext = true
    }

    private var searchResultsController: SearchResultsViewController? {
        return navigationItem.searchController?.searchResultsController as? SearchResultsViewController
    }
}

// MARK: - helpers
extension MainViewController {
    func search(_ text: String?) {
        guard let text = text, !text.isEmpty else {
            return
        }
        searchResultsController?.search(text, in: items)
    }
}
  • 刪除searchResults
  • 創(chuàng)建SearchResultsViewController對象衙传,并傳給UISearchController對象。后面點擊搜索欄的時候就會通過present方法來顯示這個界面厕九。
  • 下面是definesPresentationContext屬性的官方說明

When a context-based presentation occurs, UIKit starts at the presenting view controller and walks up the view controller hierarchy. If it finds a view controller whose value for this property is true, it asks that view controller to present the new view controller. If no view controller defines the presentation context, UIKit asks the window’s root view controller to handle the presentation.

也就是說粪牲,把它設為true的話,presentation就會以這一層為root view來展示止剖。如果保持默認值false腺阳,則會以window為root view,將導致SearchResultsViewController遮住整個屏幕(包括搜索欄)穿香。

  • obscuresBackgroundDuringPresentation可以使用默認值true來顯示半透明遮罩了亭引。把原先的賦值語句刪掉就好了。
  • 原來的search方法改為直接調用searchResultsController上的search方法皮获。

剩下的部分簡化成:

// MARK: - UITableViewDataSource
extension MainViewController {
    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return items.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        cell.textLabel?.text = items[indexPath.row]
        return cell
    }
}

// MARK: - UISearchBarDelegate
extension MainViewController : UISearchBarDelegate {
    func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
        search(searchBar.text)
    }
}

// MARK: - UISearchControllerDelegate
extension MainViewController : UISearchControllerDelegate {
    func didDismissSearchController(_ searchController: UISearchController) {
        tableView.reloadData()
    }
}

現(xiàn)在運行代碼焙蚓,輸入文字并點return,可以顯示搜索結果洒宝,并把匹配部分高亮顯示购公。看起來不錯雁歌。
此時點Cancel宏浩,再點搜索欄,輸入文字靠瞎。這時發(fā)現(xiàn)searchResultsController還顯示著上次的結果比庄。這是因為每次present用的是同一個SearchResultsViewController的實例。

個人覺得API的設計還不如改成在UISearchControllerDelegate里提供一個接口乏盐,用來返回一個UIViewController實例佳窑,作為顯示結果的界面。這樣可以在需要顯示時才去創(chuàng)建這個view controller父能。

解決這個問題神凑,只要在present的時候把搜索結果重置一下就好了。

SearchResultsViewController中添加一個reset何吝,負責重置狀態(tài)溉委。

func reset() {
    results.removeAll()
}

然后在MainViewController里響應willPresentSearchController事件

extension MainViewController : UISearchControllerDelegate {
    func willPresentSearchController(_ searchController: UISearchController) {
        searchResultsController?.reset()
    }
}

運行,妥妥的岔霸。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末薛躬,一起剝皮案震驚了整個濱河市俯渤,隨后出現(xiàn)的幾起案子呆细,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件絮爷,死亡現(xiàn)場離奇詭異趴酣,居然都是意外死亡,警方通過查閱死者的電腦和手機坑夯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進店門岖寞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人柜蜈,你說我怎么就攤上這事仗谆。” “怎么了淑履?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵隶垮,是天一觀的道長。 經常有香客問我秘噪,道長狸吞,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任指煎,我火速辦了婚禮蹋偏,結果婚禮上,老公的妹妹穿的比我還像新娘至壤。我一直安慰自己威始,他們只是感情好,可當我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布像街。 她就那樣靜靜地躺著字逗,像睡著了一般。 火紅的嫁衣襯著肌膚如雪宅广。 梳的紋絲不亂的頭發(fā)上葫掉,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天,我揣著相機與錄音跟狱,去河邊找鬼俭厚。 笑死,一個胖子當著我的面吹牛驶臊,可吹牛的內容都是我干的挪挤。 我是一名探鬼主播,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼关翎,長吁一口氣:“原來是場噩夢啊……” “哼扛门!你這毒婦竟也來了?” 一聲冷哼從身側響起纵寝,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤论寨,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發(fā)現(xiàn)了一具尸體葬凳,經...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡绰垂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了火焰。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片劲装。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖昌简,靈堂內的尸體忽然破棺而出占业,到底是詐尸還是另有隱情,我是刑警寧澤纯赎,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布纺酸,位于F島的核電站,受9級特大地震影響址否,放射性物質發(fā)生泄漏餐蔬。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一佑附、第九天 我趴在偏房一處隱蔽的房頂上張望樊诺。 院中可真熱鬧,春花似錦音同、人聲如沸词爬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽顿膨。三九已至,卻和暖如春叽赊,著一層夾襖步出監(jiān)牢的瞬間恋沃,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工必指, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留囊咏,地道東北人。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓塔橡,卻偏偏與公主長得像梅割,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子葛家,可洞房花燭夜當晚...
    茶點故事閱讀 45,060評論 2 355

推薦閱讀更多精彩內容

  • /* UIViewController is a generic controller base class th...
    DanDanC閱讀 1,814評論 0 2
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,180評論 25 707
  • 更好的閱讀體驗户辞,請到個人博客閱讀: iOS中的系統(tǒng)轉場 請忽略標題,??癞谒,本文記錄的是對下圖所示的Kind, Pre...
    CaryaLiu閱讀 2,357評論 0 1
  • 我們的身邊總有一些靜物存在底燎。 我們常把孤獨寂寞掛在嘴邊刃榨,那是我們還沒有注意到身邊還有這些靜物們的無聲伴隨。 靜物們...
    半島公子閱讀 924評論 3 6
  • 終于要回校領取畢業(yè)證书蚪,這樣就好像徹底跟大學說再見了,之前同學走的時候沒有哭迅栅,是覺得還可以見面殊校,但是如今自己在這空空...
    會飛的蝸牛閱讀 237評論 0 1