美文网首页
UIKit框架(四十) —— iOS 13中UISearchCo

UIKit框架(四十) —— iOS 13中UISearchCo

作者: 刀客传奇 | 来源:发表于2020-05-05 16:41 被阅读0次

    版本记录

    版本号 时间
    V1.0 2020.05.05 星期二

    前言

    iOS中有关视图控件用户能看到的都在UIKit框架里面,用户交互也是通过UIKit进行的。感兴趣的参考上面几篇文章。
    1. UIKit框架(一) —— UIKit动力学和移动效果(一)
    2. UIKit框架(二) —— UIKit动力学和移动效果(二)
    3. UIKit框架(三) —— UICollectionViewCell的扩张效果的实现(一)
    4. UIKit框架(四) —— UICollectionViewCell的扩张效果的实现(二)
    5. UIKit框架(五) —— 自定义控件:可重复使用的滑块(一)
    6. UIKit框架(六) —— 自定义控件:可重复使用的滑块(二)
    7. UIKit框架(七) —— 动态尺寸UITableViewCell的实现(一)
    8. UIKit框架(八) —— 动态尺寸UITableViewCell的实现(二)
    9. UIKit框架(九) —— UICollectionView的数据异步预加载(一)
    10. UIKit框架(十) —— UICollectionView的数据异步预加载(二)
    11. UIKit框架(十一) —— UICollectionView的重用、选择和重排序(一)
    12. UIKit框架(十二) —— UICollectionView的重用、选择和重排序(二)
    13. UIKit框架(十三) —— 如何创建自己的侧滑式面板导航(一)
    14. UIKit框架(十四) —— 如何创建自己的侧滑式面板导航(二)
    15. UIKit框架(十五) —— 基于自定义UICollectionViewLayout布局的简单示例(一)
    16. UIKit框架(十六) —— 基于自定义UICollectionViewLayout布局的简单示例(二)
    17. UIKit框架(十七) —— 基于自定义UICollectionViewLayout布局的简单示例(三)
    18. UIKit框架(十八) —— 基于CALayer属性的一种3D边栏动画的实现(一)
    19. UIKit框架(十九) —— 基于CALayer属性的一种3D边栏动画的实现(二)
    20. UIKit框架(二十) —— 基于UILabel跑马灯类似效果的实现(一)
    21. UIKit框架(二十一) —— UIStackView的使用(一)
    22. UIKit框架(二十二) —— 基于UIPresentationController的自定义viewController的转场和展示(一)
    23. UIKit框架(二十三) —— 基于UIPresentationController的自定义viewController的转场和展示(二)
    24. UIKit框架(二十四) —— 基于UICollectionViews和Drag-Drop在两个APP间的使用示例 (一)
    25. UIKit框架(二十五) —— 基于UICollectionViews和Drag-Drop在两个APP间的使用示例 (二)
    26. UIKit框架(二十六) —— UICollectionView的自定义布局 (一)
    27. UIKit框架(二十七) —— UICollectionView的自定义布局 (二)
    28. UIKit框架(二十八) —— 一个UISplitViewController的简单实用示例 (一)
    29. UIKit框架(二十九) —— 一个UISplitViewController的简单实用示例 (二)
    30. UIKit框架(三十) —— 基于UICollectionViewCompositionalLayout API的UICollectionViews布局的简单示例(一)
    31. UIKit框架(三十一) —— 基于UICollectionViewCompositionalLayout API的UICollectionViews布局的简单示例(二)
    32. UIKit框架(三十二) —— 替换Peek and Pop交互的基于iOS13的Context Menus(一)
    33. UIKit框架(三十三) —— 替换Peek and Pop交互的基于iOS13的Context Menus(二)
    34. UIKit框架(三十四) —— Accessibility的使用(一)
    35. UIKit框架(三十五) —— Accessibility的使用(二)
    36. UIKit框架(三十六) —— UICollectionView UICollectionViewDiffableDataSource的使用(一)
    37. UIKit框架(三十七) —— UICollectionView UICollectionViewDiffableDataSource的使用(二)
    38. UIKit框架(三十八) —— 基于CollectionView转盘效果的实现(一)
    39. UIKit框架(三十九) —— iOS 13中UISearchController 和 UISearchBar的新更改(一)

    源码

    1. Swift

    首先看下工程组织结构

    接着看下sb中的内容

    下面就是源码了

    1. MainViewController.swift
    
    import UIKit
    
    class MainViewController: UITableViewController {
      var countries = Country.countries()
      var searchController: UISearchController!
      var resultsTableViewController: ResultsTableViewController!
      var searchContinents: [String] {
        // 1
        let tokens = searchController.searchBar.searchTextField.tokens
        // 2
        return tokens.compactMap {
          ($0.representedObject as? Continent)?.description
        }
      }
      var isSearchingByTokens: Bool {
        return
          searchController.isActive &&
          searchController.searchBar.searchTextField.tokens.count > 0
      }
      
      override func viewDidLoad() {
        super.viewDidLoad()
        
        resultsTableViewController = storyboard!.instantiateViewController(withIdentifier: "resultsViewController") as? ResultsTableViewController
        resultsTableViewController.delegate = self
        
        searchController = UISearchController(searchResultsController: resultsTableViewController)
        navigationItem.searchController = searchController
        
        searchController.obscuresBackgroundDuringPresentation = false
        searchController.searchBar.placeholder = "Find a country"
        searchController.searchBar.scopeButtonTitles = Year.allCases.map { $0.description }
        searchController.searchBar.delegate = self
        searchController.searchResultsUpdater = self
        searchController.automaticallyShowsScopeBar = false
        searchController.searchBar.searchTextField.textColor = .rwGreen()
        searchController.searchBar.searchTextField.tokenBackgroundColor = .rwGreen()
      }
      
      override func numberOfSections(in tableView: UITableView) -> Int {
        return Continent.allCases.count
      }
      
      override func tableView(_ tableView: UITableView,
                              titleForHeaderInSection section: Int) -> String? {
        return Continent.allCases[section].description
      }
      
      override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        let continentForSection = Continent.allCases[section]
        return countries[continentForSection]?.count ?? 0
      }
      
      override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "country", for: indexPath) as! CountryCell
        let continentForRow = Continent.allCases[indexPath.section]
        cell.country = countries[continentForRow]?[indexPath.row]
        return cell
      }
    }
    
    // MARK: -
    
    extension MainViewController {
      func searchFor(_ searchText: String?) {
        // 1
        guard searchController.isActive else { return }
        // 2
        guard let searchText = searchText else {
          resultsTableViewController.countries = nil
          return
        }
        // 3
        let selectedYear = selectedScopeYear()
        let allCountries = countries.values.joined()
        let filteredCountries = allCountries.filter { (country: Country) -> Bool in
          // 4
          let isMatchingYear = selectedYear == Year.all.description ?
            true : (country.year.description == selectedYear)
          // 5
          let isMatchingTokens = searchContinents.count == 0 ?
            true : searchContinents.contains(country.continent.description)
          // 6
            if !searchText.isEmpty {
            return
              isMatchingYear &&
              isMatchingTokens &&
              country.name.lowercased().contains(searchText.lowercased())
          // 7
          } else if isSearchingByTokens {
            return isMatchingYear && isMatchingTokens
          }
          // 8
          return false
        }
        // 9
        resultsTableViewController.countries =
          filteredCountries.count > 0 ? filteredCountries : nil
      }
      
      func selectedScopeYear() -> String {
        guard let scopeButtonTitles = searchController.searchBar.scopeButtonTitles else {
          return Year.all.description
        }
        return scopeButtonTitles[searchController.searchBar.selectedScopeButtonIndex]
      }
      
      func showScopeBar(_ show: Bool) {
        guard searchController.searchBar.showsScopeBar != show else { return }
        searchController.searchBar.setShowsScope(show, animated: true)
        view.setNeedsLayout()
      }
    }
    
    // MARK: -
    
    extension MainViewController: UISearchBarDelegate {
      func searchBar(_ searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) {
        guard let searchText = searchController.searchBar.text else { return }
        searchFor(searchText)
      }
      
      func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
        searchFor(searchText)
        let showScope = !searchText.isEmpty
        showScopeBar(showScope)
      }
      
      func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
        resultsTableViewController.countries = nil
        showScopeBar(false)
        searchController.searchBar.searchTextField.backgroundColor = nil
      }
    }
    
    // MARK: -
    
    extension MainViewController: UISearchResultsUpdating {
      func updateSearchResults(for searchController: UISearchController) {
        // 1
        if searchController.searchBar.searchTextField.isFirstResponder {
          searchController.showsSearchResultsController = true
          // 2
          searchController.searchBar.searchTextField.backgroundColor = UIColor.rwGreen().withAlphaComponent(0.1)
        } else {
          // 3
          searchController.searchBar.searchTextField.backgroundColor = nil
        }
      }
    }
    
    // MARK: -
    
    extension MainViewController: ResultsTableViewDelegate {
      func didSelect(token: UISearchToken) {
        // 1
        let searchTextField = searchController.searchBar.searchTextField
        // 2
        searchTextField.insertToken(token, at: searchTextField.tokens.count)
        // 3
        searchFor(searchController.searchBar.text)
        showScopeBar(true)
      }
    }
    
    2. ResultsTableViewController.swift
    
    import UIKit
    
    protocol ResultsTableViewDelegate: class {
      func didSelect(token: UISearchToken)
    }
    
    class ResultsTableViewController: UITableViewController {
      var countries: [Country]? {
        didSet {
          tableView.reloadData()
        }
      }
      var  searchTokens: [UISearchToken] = []
      var isFilteringByCountry: Bool {
        return countries != nil
      }
      weak var delegate: ResultsTableViewDelegate?
      
      override func viewDidLoad() {
        super.viewDidLoad()
        makeTokens()
      }
      
      override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return isFilteringByCountry ? (countries?.count ?? 0) : searchTokens.count
      }
      
      override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        // 1
        if
          isFilteringByCountry,
          let cell = tableView.dequeueReusableCell(withIdentifier: "results", for: indexPath) as? CountryCell {
          cell.country = countries?[indexPath.row]
          return cell
        
        // 2
        } else if
          let cell = tableView.dequeueReusableCell(withIdentifier: "search", for: indexPath) as? SearchTokenCell {
          cell.token = searchTokens[indexPath.row]
          return cell
        }
    
        // 3
        return UITableViewCell()
      }
      
      override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        guard !isFilteringByCountry else { return }
        delegate?.didSelect(token: searchTokens[indexPath.row])
      }
    }
    
    // MARK: -
    
    extension ResultsTableViewController {
      func makeTokens() {
        // 1
        let continents = Continent.allCases
        searchTokens = continents.map { (continent) -> UISearchToken in
          // 2
          let globeImage = UIImage(systemName: "globe")
          let token = UISearchToken(icon: globeImage, text: continent.description)
          // 3
          token.representedObject = Continent(rawValue: continent.description)
          // 4
          return token
        }
      }
    }
    
    3. CountryCell.swift
    
    import UIKit
    
    class CountryCell: UITableViewCell {
      @IBOutlet weak var countryLabel: UILabel!
      @IBOutlet weak var yearLabel: UILabel!
      @IBOutlet weak var populationLabel: UILabel!
      
      var country: Country? {
        didSet {
          guard let country = country else { return }
          countryLabel.text = country.name
          yearLabel.text = country.year.description
          populationLabel.text = country.formattedPopulation
        }
      }
      
      required init?(coder: NSCoder) {
        super.init(coder: coder)
        selectionStyle = .none
      }
    }
    
    4. SearchTokenCell.swift
    
    import UIKit
    
    class SearchTokenCell: UITableViewCell {
      @IBOutlet weak var tokenLabel: UILabel!
      @IBOutlet weak var continentImageView: UIImageView!
      
      var token: UISearchToken! {
        didSet {
          guard let continent = token?.representedObject as? Continent else {
            return
          }
          tokenLabel.text = "Search by \(continent.description)"
          continentImageView.image = UIImage(systemName: "globe")
          continentImageView.tintColor = .rwGreen()
        }
      }
    }
    
    5. Country.swift
    
    import Foundation
    
    struct Country: Decodable {
      let name: String
      let continent: Continent
      let region: String
      let population: Int
      let year: Year
      
      enum CodingKeys: String, CodingKey {
        case name = "country"
        case continent, region, population, year
      }
    }
    
    // MARK: -
    
    extension Country {
      static func countries() -> [Continent: [Country]] {
        guard
          let url = Bundle.main.url(forResource: "population", withExtension: "json"),
          let data = try? Data(contentsOf: url)
          else {
            return [:]
        }
        
        do {
          let countries = try JSONDecoder().decode([Country].self, from: data)
          var countriesByContient: [Continent: [Country]] = [:]
          Continent.allCases.forEach { (continent) in
            countriesByContient[continent] = countries.filter {
              $0.continent == continent
            }
          }
          return countriesByContient
        } catch {
          print(error.localizedDescription)
          return [:]
        }
      }
      
      var formattedPopulation: String? {
        return NSNumber(value: population).formatted()
      }
    }
    
    6. Continent.swift
    
    enum Continent: String, CaseIterable, Decodable {
      case africa = "Africa"
      case americas = "Americas"
      case asia = "Asia"
      case europe = "Europe"
      case oceania = "Oceania"
    }
    
    // MARK: -
    
    extension Continent: CustomStringConvertible {
      var description: String { rawValue }
    }
    
    7. Year.swift
    
    enum Year: Int, CaseIterable, Decodable {
      case all = 0
      case year2018 = 2018
      case year2019 = 2019
    }
    
    // MARK: -
    
    extension Year: CustomStringConvertible {
      var description: String {
        return rawValue == 0 ? "All" : rawValue.description
      }
    }
    
    8. NSNumber+Estensions.swift
    
    import Foundation
    
    extension NSNumber {
      func formatted(withStyle style: NumberFormatter.Style = .decimal) -> String? {
        let numberFormatter = NumberFormatter()
        numberFormatter.locale = Locale.current
        numberFormatter.numberStyle = style
        return numberFormatter.string(from: self)
      }
    }
    
    9. UIColor+Extensions.swift
    
    import UIKit
    
    extension UIColor {
      static func rwGreen() -> UIColor {
        return UIColor(red: 0/255, green: 104/255, blue: 55/255, alpha: 1)
      }
    }
    

    后记

    本篇主要讲述了iOS 13UISearchControllerUISearchBar的新更改,感兴趣的给个赞或者关注~~~

    相关文章

      网友评论

          本文标题:UIKit框架(四十) —— iOS 13中UISearchCo

          本文链接:https://www.haomeiwen.com/subject/vvnmghtx.html