当前位置: > > > Swift - 省市县三级联动功能的实现(使用UIPickerView选择框)

Swift - 省市县三级联动功能的实现(使用UIPickerView选择框)

(本文代码已升级至Swift4)

省、市、县(地区)的联动选择功能在实际项目中很常见。在之前的文章(Swift - 选择框(UIPickerView)的用法)中,我介绍了 UIPickerView 的基本用法,本文演示如何实现联动功能。

1,三级联动效果图
(1)切换省时,后面市县两级数据会动态改变。
(2)切换市时,后面的县级数据会动态改变。
(3)点击“获取信息”,取得最终选择的省、市、县索引及其名字,并打印出来。
             

2,地址信息文件
首先我们要准备一个包含所有地址信息的plist数据文件(address.plist),省市县的数据是一级级嵌套的树形结构,如下图:

部分数据节选如下(完整数据见我上传的源码包):
<plist version="1.0">
    <array>
        <dict>
            <key>cities</key>
            <array>
                <dict>
                    <key>areas</key>
                    <array>
                        <string>普陀区</string>
                        <string>定海区</string>
                        <string>嵊泗县</string>
                        <string>岱山县</string>
                    </array>
                    <key>city</key>
                    <string>舟山</string>
                </dict>
                <dict>
                    <key>areas</key>
                    <array>
                        <string>萧山区</string>
                        <string>余杭区</string>
                        <string>桐庐县</string>
                        <string>淳安县</string>
                        <string>临安市</string>
                        <string>滨江区</string>
                        <string>富阳市</string>
                    </array>
                    <key>city</key>
                    <string>杭州</string>
                </dict>
            </array>
            <key>state</key>
            <string>浙江</string>
        </dict>
        <dict>
            <key>cities</key>
            <array>
                <dict>
                    <key>areas</key>
                    <array>
                        <string>连云区</string>
                        <string>新浦区</string>
                        <string>灌云县</string>
                        <string>东海县</string>
                    </array>
                    <key>city</key>
                    <string>连云港</string>
                </dict>
            </array>
            <key>state</key>
            <string>江苏</string>
        </dict>  
    </array>
</plist>

3,功能实现
(1)程序启动后,首先从 address.plist 中将所有的地址数据读取出来,存储到本地变量(数组形式)。
(2)pickerView 默认加载所有省,以及第一个省的所有市数据,以及第一个市的所有县数据(如果有的话)。
(3)通过 pickerView 选中项的改变方法 didSelectRow,取得选中的列和行索引。随后调用 reloadComponent() 方法,根据索引刷新显示对应的内容。
import UIKit

class ViewController:UIViewController, UIPickerViewDelegate, UIPickerViewDataSource{
    
    //选择器
    var pickerView:UIPickerView!
    
    //所以地址数据集合
    var addressArray = [[String: AnyObject]]()
    
    //选择的省索引
    var provinceIndex = 0
    //选择的市索引
    var cityIndex = 0
    //选择的县索引
    var areaIndex = 0
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //初始化数据
        let path = Bundle.main.path(forResource: "address", ofType:"plist")
        self.addressArray = NSArray(contentsOfFile: path!) as! Array
        
        //创建选择器
        pickerView=UIPickerView()
        //将dataSource设置成自己
        pickerView.dataSource=self
        //将delegate设置成自己
        pickerView.delegate=self
        self.view.addSubview(pickerView)
        
        //建立一个按钮,触摸按钮时获得选择框被选择的索引
        let button = UIButton(frame:CGRect(x:0, y:0, width:100, height:30))
        button.center = self.view.center
        button.backgroundColor = UIColor.blue
        button.setTitle("获取信息",for:.normal)
        button.addTarget(self, action:#selector(ViewController.getPickerViewValue),
                         for: .touchUpInside)
        self.view.addSubview(button)
    }
    
    
    //设置选择框的列数为3列,继承于UIPickerViewDataSource协议
    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return 3
    }
    
    //设置选择框的行数,继承于UIPickerViewDataSource协议
    func pickerView(_ pickerView: UIPickerView,
                    numberOfRowsInComponent component: Int) -> Int {
        if component == 0 {
            return self.addressArray.count
        } else if component == 1 {
            let province = self.addressArray[provinceIndex]
            return province["cities"]!.count
        } else {
            let province = self.addressArray[provinceIndex]
            if let city = (province["cities"] as! NSArray)[cityIndex]
                as? [String: AnyObject] {
                return city["areas"]!.count
            } else {
                return 0
            }
        }
    }
    
    //设置选择框各选项的内容,继承于UIPickerViewDelegate协议
    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int,
                    forComponent component: Int) -> String? {
            if component == 0 {
                return self.addressArray[row]["state"] as? String
            }else if component == 1 {
                let province = self.addressArray[provinceIndex]
                let city = (province["cities"] as! NSArray)[row]
                    as! [String: AnyObject]
                return city["city"] as? String
            }else {
                let province = self.addressArray[provinceIndex]
                let city = (province["cities"] as! NSArray)[cityIndex]
                    as! [String: AnyObject]
                return (city["areas"] as! NSArray)[row] as? String
            }
    }
    
    //选中项改变事件(将在滑动停止后触发)
    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int,
                    inComponent component: Int) {
        //根据列、行索引判断需要改变数据的区域
        switch (component) {
        case 0:
            provinceIndex = row;
            cityIndex = 0;
            areaIndex = 0;
            pickerView.reloadComponent(1);
            pickerView.reloadComponent(2);
            pickerView.selectRow(0, inComponent: 1, animated: false)
            pickerView.selectRow(0, inComponent: 2, animated: false)
        case 1:
            cityIndex = row;
            areaIndex = 0;
            pickerView.reloadComponent(2);
            pickerView.selectRow(0, inComponent: 2, animated: false)
        case 2:
            areaIndex = row;
        default:
            break;
        }
    }
    
    //触摸按钮时,获得被选中的索引
    @objc func getPickerViewValue(){
        //获取选中的省
        let p = self.addressArray[provinceIndex]
        let province = p["state"]!
        
        //获取选中的市
        let c = (p["cities"] as! NSArray)[cityIndex] as! [String: AnyObject]
        let city = c["city"] as! String
        
        //获取选中的县(地区)
        var area = ""
        if (c["areas"] as! [String]).count > 0 {
            area = (c["areas"] as! [String])[areaIndex]
        }
        
        //拼接输出消息
        let message = "索引:\(provinceIndex)-\(cityIndex)-\(areaIndex)\n"
            + "值:\(province) - \(city) - \(area)"
        
        //消息显示
        let alertController = UIAlertController(title: "您选择了",
                                    message: message, preferredStyle: .alert)
        let cancelAction = UIAlertAction(title: "确定", style: .cancel, handler: nil)
        alertController.addAction(cancelAction)
        self.present(alertController, animated: true, completion: nil)
    }
}
源码下载:hangge_1169.zip
评论9
  • 9楼
    2018-01-05 17:27
    小豪

    站长,这个能直接修改字体大小吗?

    站长回复

    想修改字体大小可以参考我的另一篇文章:Swift - 选择框(UIPickerView)的用法

  • 8楼
    2017-11-08 21:52
    666

    航哥,请问这个能否做到循环显示呢?谢谢!

    站长回复

    循环显示其实就是你把行数设置成一个特别大的值,然后让其默认选择中间行。过段时间我也会写篇相关文章。

  • 7楼
    2017-09-20 18:02
    66

    航哥 代码升级到swift4 之后 报错误Ambiguous use of 'subscript'
    let city = province["cities"]![cityIndex]as! [String:AnyObject] 可以更新一下代码吗

    站长回复

    代码现已更新,你可以再看下。

  • 6楼
    2017-09-20 15:47
    王强

    航哥,我在跑真机的时候出现了Ambiguous use of 'subscript'这个报错,模拟器上没问题,请问下是什么情况?

    站长回复

    版本升级语法有变。文章代码已更新,你可以再看下。

  • 5楼
    2017-09-19 13:35
    levi

    我的也报 Ambiguous use of 'subscript' ,let city = (province["cities"] as! NSArray)[row] as! [String: AnyObject] 可以解决

    站长回复

    多谢你的提醒,代码现已修正。

  • 4楼
    2017-09-04 17:15
    王大人

    航哥,这里我不用plist,数据应该怎么处理显示啊

    站长回复

    也可以在代码中直接定义成字典,不过由于层级太多,不管阅读还是维护远不如使用plist方便:

    //所有地址数据集合
    var addressArray = [["state":"浙江","cities":[["city":"杭州","areas":["萧山区","余杭区"]],["city":"舟山","areas":["普陀区","定海区"]]]],
                            ["state":"江苏","cities":[["city":"连云港","areas":["连云区","新浦区"]]]]]

  • 3楼
    2017-05-10 14:09
    chuang

    航哥,我在使用的时候出现了Ambiguous use of 'subscript'这个报错,请问下是什么情况?

    站长回复

    我又测试了下代码,没发现这个问题。我也不知道你那边是什么情况了。

  • 2楼
    2017-05-05 15:27
    小风

    航哥,有写过美国城市的三级联动选择器吗?我连网上这些三级关联数据都没找到。。。

    站长回复

    这个我也没写过,主要是我也没有数据源。

  • 1楼
    2016-12-13 10:24
    小小白

    能升级swift3吗

    站长回复

    代码已更新,你可以再看下。