经验篇

Swift 5 防高德地图搜索

2020-05-20  本文已影响0人  Jerome_e7ec

一款好的搜索展示动画,离不开前人的辛勤劳动

老规矩,先上效果图:


demo.gif

可搜索,可滑动,是不是足够满足你的需要😎

实现

  1. 首先你需要一个TableView,并且监听上下滑动手势。⚠️需要设置滑动事件的代理

为什么捏,你先想想😄

    fileprivate lazy var tableView: UITableView = {
        let table = let table = UITableView(frame: CGRect(x: 0, y: 0, width: view.bounds.width, height: view.bounds.height - Y2), style: .plain)
        table.delegate = self
        table.dataSource = self
        table.bounces = false
        table.isScrollEnabled = false
        table.tableFooterView = UIView()
        
        let down = UISwipeGestureRecognizer(target: self, action: #selector(swipe(_:)))
        down.direction = .down
        down.delegate = self
        table.addGestureRecognizer(down)
        
        let up = UISwipeGestureRecognizer(target: self, action: #selector(swipe(_:)))
        up.direction = .up
        up.delegate = self
        table.addGestureRecognizer(up)
        return table
    }()
  1. 有了列表,我们还需要什么呢?🤔那当然是搜索框啦
    fileprivate lazy var searchController: UISearchController = {
        let searchController = UISearchController(searchResultsController: nil)
        searchController.searchBar.placeholder = "搜索"
        searchController.searchBar.searchBarStyle = .minimal
        searchController.searchBar.barTintColor = .white
        // 去掉searchBar上下的两条黑线 这里需要设置任意的图片去覆盖黑线
        searchController.searchBar.setBackgroundImage(UIColor.clear.jx_toImage(size: CGSize(width: 1, height: 1)), for: .any, barMetrics: .default)
        searchController.searchBar.sizeToFit()
        // 设置开始搜索时背景显示与否
        searchController.dimsBackgroundDuringPresentation = false
        
        searchController.searchBar.delegate = self
        return searchController
    }()
  1. 有了列表和搜索框当然是开始完善我们的搜索页啦,填数据什么的我想你们都在话下了,就不写了,主要是讲下列表如何关联搜索框。
    UISearchBar默认高度为44,把它放在一个自定义的searchView,通过设置searchView在backgroundView中的位置,让它看起来好像是searchBar的高度在改变😎
    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let width = tableView.bounds.width
        let backgroundView = UIView(frame: CGRect(x: 0, y: 0, width: width, height: 20 + 44))
        backgroundView.backgroundColor = .white
        
        let searchView = UIView(frame: CGRect(x: 0, y: 15, width: width, height: 44))
        searchView.addSubview(searchController.searchBar)
        backgroundView.addSubview(searchView)
        
        let hintV = UIView(frame: CGRect(x: (width - 40) / 2.0, y: 10, width: 40, height: 4))
        hintV.backgroundColor = UIColor.lightGray.withAlphaComponent(0.5)
        hintV.layer.cornerRadius = hintV.bounds.height/2
        hintV.layer.masksToBounds = true
        backgroundView.addSubview(hintV)
        
        return backgroundView
    }
  1. 那么好,到这里我们已经把搜索视图准备好了,现在该处理列表的滑动事件。根据上下滑动事件获取相对应的停止Y坐标,by the way,手动加了个回弹动画👻
    // table可滑动时,swipe默认不再响应 所以要打开
    @objc func swipe(_ swipe: UISwipeGestureRecognizer) {
        guard let shadowView = self.shadowView else {
            return
        }
        var stopY: CGFloat = 0
        var animateY: CGFloat = 0
        let margin: CGFloat = 10 // 动画的幅度
        let offsetY = shadowView.frame.origin.y // 这是上一次Y的位置
        
        if swipe.direction == .down {
            // 当vc.table滑到顶部且是下滑时,让vc.table禁止滑动
            if tableView.contentOffset.y == 0 {
                tableView.isScrollEnabled = false
            }
            
            if offsetY >= Y1 {
                // 停在Y2的位置
                stopY = Y2
            } else {
                stopY = Y1
            }
            animateY = stopY + margin
        }
        if swipe.direction == .up {
            if offsetY <= Y2 {
                stopY = Y1
                // 当停在Y1位置且是上划时,让vc.table不再禁止滑动
                tableView.isScrollEnabled = true
            } else {
                stopY = Y2
            }
            animateY = stopY - margin
        }
        
        // 弹性动画
        let bounce = CGRect(x: 0, y: animateY, width: view.bounds.width, height: mScreenH)
        let to = CGRect(x: 0, y: stopY, width: bounce.width, height: bounce.height)
        UIView.animate(withDuration: 0.4, animations: {
            shadowView.frame = bounce
        }) { finished in
            UIView.animate(withDuration: 0.2, animations: {
                shadowView.frame = to
            })
        }
        // 记录shadowView在第一个视图中的位置
        self.offsetY = stopY
    }
  1. 动态改变tableiView的高度
    fileprivate var offsetY: CGFloat = Y2 {
        didSet {
            tableView.frame.size.height = view.bounds.height - offsetY
        }
    }

讲到这里就需要提到上文的手势代理干嘛用了🤭

  1. 首先在滑动的时候取消搜索状态,让搜索框跟随滚动
  2. 根据列表状态判断是否让swipe响应手势事件
   func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        cancelSearch()
        
        // 当table允许滚动且offsetY不为0时,让swipe响应
        if tableView.isScrollEnabled == true && tableView.contentOffset.y != 0 {
            return false
        }
        if tableView.isScrollEnabled == true {
            return true
        }
        return false
    }

有同学可能会有疑问了,我点击搜索框时为什么会出现上弹动画呢,这里就给你们讲解一哈。不知道大家有没有注意到在我们设置UISearchControllersearchBar属性时,我们设置了搜索条的代理delegate,这就是啦

    func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool {
        // 如果点击时,shadowView的y坐标 不在Y1的位置,
        if offsetY > Y1+1 {
            UIView.animate(withDuration: 0.4, animations: {
                self.shadowView.frame = CGRect(x: 0, y: Y1, width: self.view.frame.width, height: UIScreen.main.bounds.height)
            }) { finished in
                // 呼出键盘。  一定要在动画结束后调用,否则会出错
                self.searchController.searchBar.becomeFirstResponder()
            }
            // 更新offsetY
            offsetY = self.shadowView.frame.origin.y
            return false
        }
        return true
    }

以上我们都是在将列表展示逻辑,那到底要怎么显示呢,莫及莫及,且听我慢慢道来(别打我🌚

相信看到这里的朋友肯定对出现在上文的shadowView表示不理解,这个视图哪来的啊,这也是最后需要做的一步,也就是展示。

class LocationSearchVC: UIViewController {
    fileprivate lazy var searchResultVC: SearchResultVC = {
        let vc = SearchResultVC(shadowView)
        return vc
    }()
    fileprivate lazy var shadowView: UIView = {
        let v = UIView()
        v.layer.shadowColor = UIColor.black.cgColor
        v.layer.shadowRadius = 10
        v.layer.shadowOffset = CGSize(width: 5, height: 5)
        v.layer.shadowOpacity = 0.8
        return v
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .yellow
        addChild(searchResultVC)
        shadowView.addSubview(searchResultVC.view)
        view.addSubview(shadowView)
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesBegan(touches, with: event)
        searchResultVC.cancelSearch()
    }
}

下面放出完整代码

import Foundation

private let Y1 = mScreenH / 3
private let Y2 = mScreenH / 3 * 2
class SearchResultVC: UIViewController {
    fileprivate lazy var tableView: UITableView = {
        let table = UITableView(frame: CGRect(x: 0, y: 0, width: view.bounds.width, height: view.bounds.height - Y2), style: .plain)
        table.delegate = self
        table.dataSource = self
        table.bounces = false
        table.isScrollEnabled = false
        table.tableFooterView = UIView()
        
        let down = UISwipeGestureRecognizer(target: self, action: #selector(swipe(_:)))
        down.direction = .down
        down.delegate = self
        table.addGestureRecognizer(down)
        
        let up = UISwipeGestureRecognizer(target: self, action: #selector(swipe(_:)))
        up.direction = .up
        up.delegate = self
        table.addGestureRecognizer(up)
        return table
    }()
    fileprivate lazy var searchController: UISearchController = {
        let searchController = UISearchController(searchResultsController: nil)
        searchController.searchBar.placeholder = "搜索"
        searchController.searchBar.searchBarStyle = .minimal
        searchController.searchBar.barTintColor = .white
        // 去掉searchBar上下的两条黑线
        searchController.searchBar.setBackgroundImage(UIColor.clear.jx_toImage(size: CGSize(width: 1, height: 1)), for: .any, barMetrics: .default)
        searchController.searchBar.sizeToFit()
        // 设置开始搜索时背景显示与否
        searchController.dimsBackgroundDuringPresentation = false
        
        searchController.searchBar.delegate = self
        return searchController
    }()
    var dataArray = ["11", "222", "3333", "abcdd", "222", "3333", "abcdd", "11", "222", "3333", "abcdd", "222", "3333", "abcdd"]
    fileprivate var offsetY: CGFloat = Y2 {
        didSet {
            tableView.frame.size.height = view.bounds.height - offsetY
        }
    }
    fileprivate var shadowView: UIView!
    /// 当前View所在父View
    init(_ parentView: UIView) {
        super.init(nibName: nil, bundle: nil)
        shadowView = parentView
        shadowView.frame = CGRect(x: 0, y: offsetY, width: view.bounds.width, height: view.bounds.height)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
        view.clipsToBounds = true
        view.layer.cornerRadius = 10
        
        view.addSubview(tableView)
    }
    /// 取消搜索
    func cancelSearch() {
        let cancelBtn = searchController.searchBar.value(forKey: "cancelButton") as? UIButton
        cancelBtn?.sendActions(for: .touchUpInside)
    }
}
fileprivate extension SearchResultVC {
    // table可滑动时,swipe默认不再响应 所以要打开
    @objc func swipe(_ swipe: UISwipeGestureRecognizer) {
        guard let shadowView = self.shadowView else {
            return
        }
        var stopY: CGFloat = 0
        var animateY: CGFloat = 0
        let margin: CGFloat = 10 // 动画的幅度
        let offsetY = shadowView.frame.origin.y // 这是上一次Y的位置
        
        if swipe.direction == .down {
            // 当vc.table滑到顶部且是下滑时,让vc.table禁止滑动
            if tableView.contentOffset.y == 0 {
                tableView.isScrollEnabled = false
            }
            
            if offsetY >= Y1 {
                // 停在Y2的位置
                stopY = Y2
            } else {
                stopY = Y1
            }
            animateY = stopY + margin
        }
        if swipe.direction == .up {
            if offsetY <= Y2 {
                stopY = Y1
                // 当停在Y1位置且是上划时,让vc.table不再禁止滑动
                tableView.isScrollEnabled = true
            } else {
                stopY = Y2
            }
            animateY = stopY - margin
        }
        
        // 弹性动画
        let bounce = CGRect(x: 0, y: animateY, width: view.bounds.width, height: mScreenH)
        let to = CGRect(x: 0, y: stopY, width: bounce.width, height: bounce.height)
        UIView.animate(withDuration: 0.4, animations: {
            shadowView.frame = bounce
        }) { finished in
            UIView.animate(withDuration: 0.2, animations: {
                shadowView.frame = to
            })
        }
        // 记录shadowView在第一个视图中的位置
        self.offsetY = stopY
    }
}
extension SearchResultVC: UITableViewDataSource, UITableViewDelegate {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        dataArray.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let identifier = "SearchResultCell"
        var cell = tableView.dequeueReusableCell(withIdentifier: identifier)
        if cell == nil {
            cell = UITableViewCell(style: .default, reuseIdentifier: identifier)
            cell?.selectionStyle = .none
            cell?.backgroundColor = .white
        }
        cell?.textLabel?.text = dataArray[indexPath.row]
        return cell!
    }
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 60
    }
    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return 20 + 44
    }
    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let width = tableView.bounds.width
        let backgroundView = UIView(frame: CGRect(x: 0, y: 0, width: width, height: 20 + 44))
        backgroundView.backgroundColor = .white
        
        let searchView = UIView(frame: CGRect(x: 0, y: 15, width: width, height: 44))
        searchView.addSubview(searchController.searchBar)
        backgroundView.addSubview(searchView)
        
        let hintV = UIView(frame: CGRect(x: (width - 40) / 2.0, y: 10, width: 40, height: 4))
        hintV.backgroundColor = UIColor.lightGray.withAlphaComponent(0.5)
        hintV.layer.cornerRadius = hintV.bounds.height/2
        hintV.layer.masksToBounds = true
        backgroundView.addSubview(hintV)
        
        return backgroundView
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        cancelSearch()
    }
}
extension SearchResultVC: UISearchBarDelegate {
    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
        if searchText.count == 0 {
            cancelSearch()
        }
    }
    func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
        let textStr = searchBar.text ?? ""
        searchBar.text = ""
        
        // 插入路径的同时,要同步插入数据
        dataArray.insert(textStr, at: 0)
        let index = IndexPath(row: 0, section: 0)
        tableView.insertRows(at: [index], with: .bottom)
    }
    func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool {
        // 如果点击时,shadowView的y坐标 不在Y1的位置,
        if offsetY > Y1+1 {
            UIView.animate(withDuration: 0.4, animations: {
                self.shadowView.frame = CGRect(x: 0, y: Y1, width: self.view.frame.width, height: UIScreen.main.bounds.height)
            }) { finished in
                // 呼出键盘。  一定要在动画结束后调用,否则会出错
                self.searchController.searchBar.becomeFirstResponder()
            }
            // 更新offsetY
            offsetY = self.shadowView.frame.origin.y
            return false
        }
        return true
    }
}

extension SearchResultVC: UIGestureRecognizerDelegate {
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        cancelSearch()
        
        // 当table允许滚动且offsetY不为0时,让swipe响应
        if tableView.isScrollEnabled && tableView.contentOffset.y != 0 {
            return false
        }
        if tableView.isScrollEnabled {
            return true
        }
        return false
    }
}
上一篇下一篇

猜你喜欢

热点阅读