Swift.地址选择器

效果图

实现效果:

controller弹出时:半透明背景渐变展示.地址选择器从下方弹出.

地址选择器:以省份,城市,地区三级进行选择,数据来自本地plist文件.有12个热门城市供快速选择,选择错误可以回选.

选择地区时进行将数据回调到上一控制器,点击页面空白区域退出controller.

controller消失时:背景渐变消失,地址选择器向下退出.

实现思路:

本质上来说这是一个复杂版的日期选择器,有关弹出动画完全可以拿过来就用.

所以主要复杂的点就是这个自定制的地址选择器.

具体实现是写一个view其中包含tableView.给view三种type.来决定它当前的状态.然后根据type的改变来修改他的样式和数据展示,以及cell被点击时执行方法.

实现方式:

1.找到数据源,声明model将数据转成对象.

2.实现地址选择器的样式,数据展示.

3.实现我们需要的转场视图动画.

4.将地址选择器加入一个ViewController,并修改其转场动画代理,使其使用自定制转场动画.

5.实现地址选择器的功能,以及交互优化.


1.找到数据源,声明model将数据转成对象.

数据来自网络,一个plist文件.将其转换成model进行保存.

struct EWCountryModel {
    var countryDictionary: [String:EWProvincesModel] = [:]
    var provincesArray: [String] = []
    init(dic: [String:[String:[String]]]) {
        for (key,value) in dic {
            let model = EWProvincesModel(dic: value)
            countryDictionary[key] = model
            provincesArray.append(key)
        }
    }
}
struct EWProvincesModel {
    var provincesDictionary: [String: EWCityModel] = [:]
    var cityArray: [String] = []
    init(dic:[String: [String]]) {
        for (key,value) in dic {
            let model = EWCityModel(Arr: value)
            provincesDictionary[key] = model
            cityArray.append(key)
        }
    }
}
struct EWCityModel {
    var areaArray: [String] = []
    init(Arr: [String]) {
        for str in Arr {
            areaArray.append(str)
        }
    }
}

    /// 从area.plist获取全部地区数据
    private func initLocationData() {
        guard let dic = NSDictionary(contentsOfFile: Bundle.main.path(forResource: "area", ofType: "plist") ?? "") as? [String:[String:[String]]] else {
            return
        }
        locationModel = EWCountryModel(dic: dic)
        dataArray = locationModel?.provincesArray
    }

2.实现地址选择器的样式,数据展示.

  private func buildTitleScrollView() {
        if titleSV != nil {
            titleSV.removeFromSuperview()
        }
        buttonArr = []
        titleSV = UIScrollView(frame: CGRect(x: 0, y: 72, width: UIScreen.main.bounds.width, height: 44))
        self.underLine = UIView(frame: CGRect(x: 0, y: 40, width: 30, height: 2))
        self.underLine.backgroundColor = selectColor
        for i in 0..<3 {
            let button = UIButton(frame: CGRect(x: 24 + CGFloat(i) * (UIScreen.main.bounds.width - 47) / 3, y: 0, width: UIScreen.main.bounds.width / 3, height: 44))
            button.tag = Int(i)
            if i == 1 {
                button.isSelected = true
                underLine.center.x = button.center.x
            }
            button.setTitle("请选择", for: .normal)
            button.setTitleColor(UIColor(red: 51/255, green: 51/255, blue: 51/255, alpha: 1), for: .normal)
            button.setTitleColor(selectColor, for: .selected)
            button.titleLabel?.font = UIFont.systemFont(ofSize: 12)
            button.titleLabel?.adjustsFontSizeToFitWidth = true
            button.addTarget(self, action: #selector(onClickTitlebutton(sender:)), for: .touchUpInside)
            buttonArr.append(button)
            titleSV.addSubview(button)
        }
        titleSV.showsVerticalScrollIndicator = false
        titleSV.addSubview(self.underLine)
        titleSV.contentSize = CGSize(width: UIScreen.main.bounds.width, height: 44)
        titleSV.isHidden = true
        self.addSubview(titleSV)
    }
  private func drawTableView() {
        self.addSubview(tableView)
        tableView.delegate = self
        tableView.dataSource = self
        tableView.separatorStyle = .none
        tableView.tableHeaderView = tableViewHeaderView
        tableView.showsVerticalScrollIndicator = false
        tableView.register(EWAddressPickViewTableViewCell.self, forCellReuseIdentifier: EWAddressPickViewTableViewCell.identifier)
        tableView.register(EWAddressPickViewFirstTableViewCell.self, forCellReuseIdentifier: EWAddressPickViewFirstTableViewCell.identifier)
    }

3.实现我们需要的转场视图动画.

   //EWAddressPickerViewController的推出和取消动画
class EWAddressPickerPresentAnimated: NSObject,UIViewControllerAnimatedTransitioning {

    var type: EWAddressPickerPresentAnimateType = .present

    init(type: EWAddressPickerPresentAnimateType) {
        self.type = type
    }
    /// 动画时间
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return 0.3
    }
    /// 动画效果
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {

        switch type {
        case .present:
            guard let toVC: EWAddressViewController = transitionContext.viewController(forKey: .to) as? EWAddressViewController else {
                return
            }
            let toView = toVC.view

            let containerView = transitionContext.containerView
            containerView.addSubview(toView!)

            toVC.containV.transform = CGAffineTransform(translationX: 0, y: (toVC.containV.frame.height))

            UIView.animate(withDuration: 0.25, animations: {
                /// 背景变色
                toVC.backgroundView.alpha = 1.0
                /// addresspicker向上推出
                toVC.containV.transform =  CGAffineTransform(translationX: 0, y: -10)
            }) { (_) in
                UIView.animate(withDuration: 0.2, animations: {
                    /// transform初始化
                    toVC.containV.transform = CGAffineTransform.identity
                }, completion: { (_) in
                    transitionContext.completeTransition(true)
                })
            }
        case .dismiss:
            guard let toVC: EWAddressViewController = transitionContext.viewController(forKey: .from) as? EWAddressViewController else {
                return
            }

            UIView.animate(withDuration: 0.25, animations: {
                toVC.backgroundView.alpha = 0.0
                /// addresspicker向下推回
                toVC.containV.transform =  CGAffineTransform(translationX: 0, y: (toVC.containV.frame.height))
            }) { (_) in
                transitionContext.completeTransition(true)
            }
        }
    }
}

4.将地址选择器加入一个ViewController,并修改其转场动画代理,使其使用自定制转场动画.

public lazy var containV: EWAddressPickView = {
        let view = EWAddressPickView(frame: CGRect(x: 0, y: UIScreen.main.bounds.height - 550, width: UIScreen.main.bounds.width, height: 550),selectColor: selectColor)
        view.backOnClickCancel = {
            self.onClickCancel()
        }
        /// 成功选择后将数据回调,并推出视图
        view.backLocationString = { (address,province,city,area) in
            if self.backLocationStringController != nil {
                self.backLocationStringController!(address,province,city,area)
                self.onClickCancel()
            }
        }
        return view
    }()

// MARK: - 转场动画delegate
extension EWAddressViewController:UIViewControllerTransitioningDelegate {
    /// 推入动画
    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        let animated = EWAddressPickerPresentAnimated(type: .present)
        return animated
    }
    /// 推出动画
    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        let animated = EWAddressPickerPresentAnimated(type: .dismiss)
        return animated
    }
}

5.实现地址选择器的功能,以及交互优化.

主要复杂的部分,只能展示其中一部分代码,具体实现还是要在demo中看.

private var tableViewType: EWLocationPickViewTableViewType = .provinces {
        didSet {
            switch tableViewType {
            case .provinces:
                /// 选择省份时,有上面的热门城市view.没有滚动选择type的titleScrollView.没有已选择label.
                self.tableView.tableHeaderView = tableViewHeaderView
                self.tableView.frame = CGRect(x: 0, y: 42, width: UIScreen.main.bounds.width, height: 458)
                self.titleSV.isHidden = true
                self.leftLabel.isHidden = true
                /// 将所有选中数据清空
                self.provincesModel = nil
                self.selectedProvince = ""
                self.selectedCity = ""
                self.selectedArea = ""
                self.cityModel = nil
                // 将titleSV中所有button的title重置
                // 并将第一个button设置为选中状态,已保证选择城市后button下的横线有滚动效果.
                for button in buttonArr {
                    button.setTitle("请选择", for: .normal)
                    button.isSelected = false
                    if button.tag == 0 {
                        button.isSelected = true
                    }
                }
                self.underLine.center = CGPoint(x: self.buttonArr[1].center.x, y: self.underLine.center.y)
                self.dataArray = locationModel?.provincesArray
                self.tableView.reloadData()
            case .city:
                /// 选择城市时没有热门城市view,并将titleSV显示出来
                self.tableView.tableHeaderView = UIView()
                self.tableView.frame = CGRect(x: 0, y: 136, width: UIScreen.main.bounds.width, height: 367)
                self.titleSV.isHidden = false
                self.leftLabel.isHidden = false
                /// 将省份选择保留,将城市与地区数据清空
                self.selectedCity = ""
                self.selectedArea = ""
                self.cityModel = nil
                /// 通过修改titleSV中button的选中状态来修改它的颜色
                for button in buttonArr {
                    button.isSelected = false
                    if button.tag != 0 {
                        button.setTitle("请选择", for: .normal)
                    }
                    if button.tag == 1 {
                        button.isSelected = true
                    }
                }
                /// 滚动titleSV中button下滚动的Line
                UIView.animate(withDuration: 0.3, animations: {() -> Void in
                    self.underLine.center = CGPoint(x: self.buttonArr[1].center.x, y: self.underLine.center.y)
                })
                self.dataArray = provincesModel?.cityArray
                self.tableView.reloadData()
            case .area:
                /// 选择地区时没有上方热门城市View,有titleSV
                self.tableView.tableHeaderView = UIView()
                self.tableView.frame = CGRect(x: 0, y: 136, width: UIScreen.main.bounds.width, height: 367)
                self.titleSV.isHidden = false
                self.leftLabel.isHidden = false
                /// 通过修改titleSV中button的选中状态来修改它的颜色
                for button in buttonArr {
                    button.isSelected = false
                    if button.tag == 2 {
                        button.isSelected = true
                    }
                }
                /// 滚动titleSV中button下滚动的Line
                UIView.animate(withDuration: 0.3, animations: {() -> Void in
                    self.underLine.center = CGPoint(x: self.buttonArr[2].center.x, y: self.underLine.center.y)
                })
                self.dataArray = cityModel?.areaArray
                self.tableView.reloadData()
            }
        }
    }

使用方法:

将EWAddressPicker文件夹拖入项目,调用时:

let addressPicker = EWAddressViewController()
/*** 可使用这种init方法自定制选中颜色,不填写selectColor默认颜色为UIColor(red: 79/255, green: 176/255, blue: 255/255, alpha: 1),蓝色
let addressPicker = EWAddressViewController(selectColor: UIColor.yellow)
*/
// 返回选择数据,地址,省,市,区
addressPicker.backLocationStringController = { (address,province,city,area) in
    self.label.text = address
}
self.present(addressPicker, animated: true, completion: nil)

demo地址:AddressPicker.求star.

OC版本:OC.地址选择器.

有问题欢迎探讨.


于2019.02.21进行代码格式优化以及更新语言版本至swift4.2

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,591评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,448评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,823评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,204评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,228评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,190评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,078评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,923评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,334评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,550评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,727评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,428评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,022评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,672评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,826评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,734评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,619评论 2 354

推荐阅读更多精彩内容

  • 一、姚剧闹新春 这是老一辈人传下来的习俗了,每到大年初一,位于老姚北、新慈西、上虞东的地方总是会上演姚剧,也就是老...
    Jane_b70f阅读 300评论 0 1
  • 12月的步伐总是来得让人觉得有些快。就像急行的侍者,从你身后赶了上来,拍拍你的肩,告诉你一年又结束了。走在大街上,...
    LostAbaddon阅读 370评论 1 2
  • 前几天一客户过来检查灯光不亮 看了一下是专用的 暂时没有 就跟客户沟通 等配件来了 给他联系 今天过来换了 总结 ...
    刘磊stely阅读 115评论 0 0
  • 银月满盏 愁绪千山卷 欲寄相思望路远 梦里故园花散 遥想旧日轻狂 懵懂不解离殇 而今征程漫漫 犹念遍野禾香 ——2...
    空修阅读 374评论 6 16