美文网首页经验篇
Swift 5 防高德地图搜索

Swift 5 防高德地图搜索

作者: Jerome_e7ec | 来源:发表于2020-05-20 17:36 被阅读0次

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

    老规矩,先上效果图:


    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
        }
    }
    

    相关文章

      网友评论

        本文标题:Swift 5 防高德地图搜索

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