iOS省市区三级联动实现

先上图,效果图如下

效果图

省市区三级联动的主要实现思路如下:

1. 新建一个集成UIViewview并重写其init方法并布局,代码如下

/// lastContent为调用的时候传进来的省市区名称数组 eg:`@[@"江苏省", @"南京市", @"江宁区"]`
- (instancetype)initWithLastContent:(NSArray *)lastContent {
    if ([super init]) {
        self.frame = CGRectMake(0, 0, kScreenWidth, kScreenHeight);
        if (lastContent) {
            self.currentSelectProvince = lastContent.firstObject;
            self.currentSelectCity = lastContent[1];
            self.currentSelectArea = lastContent.lastObject;
        }
        [self setupView];  // 布局
        [self HmGetArea];  // 解析数据
    }
    return self;
}

/// 设置view
- (void)setupView {
    // 背景
    self.bgV = [UIButton buttonWithType:(UIButtonTypeSystem)];
    _bgV.frame = CGRectMake(0, 0, kScreenWidth, kScreenHeight);
    _bgV.backgroundColor = HmColorRGBA(0, 0, 0, 0.4);
    [_bgV setTitle:@"" forState:(UIControlStateNormal)];
    [_bgV addTarget:self action:@selector(cancelAction:) forControlEvents:(UIControlEventTouchUpInside)];
    [self addSubview:_bgV];
    // 承载view
    UIView *vv = [[UIView alloc] initWithFrame:CGRectMake(20, (kScreenHeight - kPickerViewHeight - 80) / 2, kScreenWidth - 40, kPickerViewHeight + 80)];
    vv.layer.cornerRadius = 10;
    vv.layer.masksToBounds = YES;
    vv.backgroundColor = [UIColor whiteColor];
    [self addSubview:vv];
    // title
    UILabel *lblTitle = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, vv.hm_width, kTitleHeight)];
    lblTitle.textColor = HmColorRGB(51, 51, 51);
    lblTitle.backgroundColor = [UIColor clearColor];
    lblTitle.text = @"选择邮寄地区";
    lblTitle.textAlignment = NSTextAlignmentCenter;
    lblTitle.font = [UIFont systemFontOfSize:14];
    [vv addSubview:lblTitle];
    // PickerView
    self.pickerV = [[UIPickerView alloc] initWithFrame:CGRectMake(0, lblTitle.hm_bottom, vv.hm_width, kPickerViewHeight)];
    self.pickerV.frame = CGRectMake(0, lblTitle.hm_bottom, vv.hm_width, kPickerViewHeight);
    self.pickerV.delegate = self;
    self.pickerV.dataSource = self;
    self.pickerV.backgroundColor = [UIColor clearColor];
    [vv addSubview:_pickerV];
    // 分割线(横)
    UILabel *lblLineH = [[UILabel alloc] initWithFrame:CGRectMake(0, _pickerV.hm_bottom, vv.hm_width, 1)];
    lblLineH.backgroundColor = [UIColor groupTableViewBackgroundColor];
    [vv addSubview:lblLineH];
    // 取消
    UIButton *btnCancel = [UIButton buttonWithType:(UIButtonTypeCustom)];
    btnCancel.frame = CGRectMake(0, lblLineH.hm_bottom, vv.hm_width / 2 - 0.5, vv.hm_height - lblLineH.hm_bottom);
    btnCancel.backgroundColor = [UIColor clearColor];
    [btnCancel setTitle:@"取消" forState:(UIControlStateNormal)];
    [btnCancel setTitleColor:HmColorRGB(102, 102, 102) forState:(UIControlStateNormal)];
    [btnCancel addTarget:self action:@selector(cancelAction:) forControlEvents:(UIControlEventTouchUpInside)];
    btnCancel.titleLabel.font = [UIFont systemFontOfSize:15];
    [vv addSubview:btnCancel];
    // 分割线(竖)
    UILabel *lblLineV = [[UILabel alloc] initWithFrame:CGRectMake(btnCancel.hm_right, lblLineH.hm_bottom, 1, btnCancel.hm_height)];
    lblLineV.backgroundColor = [UIColor groupTableViewBackgroundColor];
    [vv addSubview:lblLineV];
    // 确定
    UIButton *btnSure = [UIButton buttonWithType:(UIButtonTypeCustom)];
    btnSure.frame = CGRectMake(lblLineV.hm_right, lblLineH.hm_bottom, vv.hm_width - lblLineV.hm_right, btnCancel.hm_height);
    btnSure.backgroundColor = [UIColor clearColor];
    [btnSure setTitle:@"确定" forState:(UIControlStateNormal)];
    [btnSure setTitleColor:HmColorRGB(83, 184, 255) forState:(UIControlStateNormal)];
    [btnSure addTarget:self action:@selector(sureAction:) forControlEvents:(UIControlEventTouchUpInside)];
    btnSure.titleLabel.font = [UIFont systemFontOfSize:15];
    [vv addSubview:btnSure];
}

2. 省市区数据都是txt文件保存,那么第一步要做的就是把txt文档中的数据拉出来并转换为模型。

数据源文档中的省市区数据为如下形式的json数据
[
    {
      "name": "北京市",
      "city": [
        {
          "name": "北京市",
          "area": [
            "东城区",
            "西城区",
            "崇文区",
            "宣武区",
            "朝阳区",
            "丰台区",
            "石景山区",
            "海淀区",
            "门头沟区",
            "房山区",
            "通州区",
            "顺义区",
            "昌平区",
            "大兴区",
            "平谷区",
            "怀柔区",
            "密云县",
            "延庆县"
          ]
        }
      ]
    },
    {
      "name": "天津市",
      "city": [
        {
          "name": "天津市",
          "area": [
            "和平区",
            "河东区",
            "河西区",
            "南开区",
            "河北区",
            "红桥区",
            "塘沽区",
            "汉沽区",
            "大港区",
            "东丽区",
            "西青区",
            "津南区",
            "北辰区",
            "武清区",
            "宝坻区",
            "宁河县",
            "静海县",
            "蓟  县"
          ]
        }
      ]
    },...

建立的两个模型分别如下图


一级

二级

转换代码如下

    NSData *data = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"aiai_area.txt" ofType:nil]];
    NSArray *allArr = [HmAddressModel arrayOfModelsFromData:data error:nil];
    self.allDataArr = [NSArray arrayWithArray:allArr];

3. 在当前页面声明三个参数分别存储当前选中的省市区,如果没有选中,则省市区分别默认为所有数据中第一个省、第一个省的第一个市、第一个省的第一个市的第一个区。同时还要声明三个数组分别存储省数组、市数组、区数组。

@property (nonatomic, strong) NSMutableArray *provinceArr;  // 所有的省数组
@property (nonatomic, strong) NSMutableArray *cityArr;  // 当前省下属的所有的市
@property (nonatomic, strong) NSMutableArray *areaArr; // 当前市下属的所有区

@property (nonatomic, strong) NSString *currentSelectProvince;  // 当前选中的省名称
@property (nonatomic, strong) NSString *currentSelectCity;  // 当前选中的市名称
@property (nonatomic, strong) NSString *currentSelectArea;  // 当前选中的区名称

4. 实现方法来计算当前的省数组、市数组、区数组数据

/// 计算当前市区数组
- (void)calculationCityAreaArr {
    // 省市区数组数据清空
    [self.provinceArr removeAllObjects];
    [self.cityArr removeAllObjects];
    [self.areaArr removeAllObjects];
    // 如果当前选中的省为nil,则默认设置为所有数据中的第一个省
    if (!self.currentSelectProvince) {
        self.currentSelectProvince = ((HmAddressModel *)self.allDataArr[0]).name;
    }
    for (HmAddressModel *model in self.allDataArr) {
        [self.provinceArr addObject:model.name];
        if ([self.currentSelectProvince isEqualToString:model.name]) {
            // 如果当前选中的市为nil,则默认为当前省下属的第一个市
            if (!self.currentSelectCity) {
                self.currentSelectCity = ((HmAddressCityModel *)model.city[0]).name;
            }
            for (HmAddressCityModel *mo in model.city) {
                [self.cityArr addObject:mo.name];
                if ([mo.name isEqualToString:self.currentSelectCity]) {
                    // 如果当前选中的区为nil,则默认为当前市下属的第一个区
                    if (!self.currentSelectArea) {
                        self.currentSelectArea = mo.area.firstObject;
                    }
                    for (NSString *aa in mo.area) {
                        [self.areaArr addObject:aa];
                    }
                }
            }
        }
    }
    [self.pickerV reloadAllComponents];
}

5. 有了数据,接下来就是要实现pickerView的代理方法了,代码如下

#pragma mark -- UIPickerView Delegate
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
    return 3;  // 返回三列 省、市、区
}

- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
    if (component == 0) {
        return self.provinceArr.count;
    }else if (component == 1) {
        return self.cityArr.count;
    }else {
        return self.areaArr.count;
    }
}

- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component {
    if (component == 0) {
        return self.provinceArr[row];
    }else if (component == 1) {
        return self.cityArr[row];
    }else {
        return self.areaArr[row];
    }
}

- (CGFloat)pickerView:(UIPickerView *)pickerView rowHeightForComponent:(NSInteger)component {
    return 30;
}

- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {
    if (component == 0) {
        // 滑动省一列,市和区的数据都要改变,就存储选中的省的数据,将市和区的选中数据置nil,然后通过calculationCityAreaArr 方法重新计算市区数组,之后将市和区的列滚动到特定的位置(在计算方法中如果市或区的数据为空,会默认为第一个的)
        self.currentSelectProvince = self.provinceArr[row];
        self.currentSelectCity = nil;
        self.currentSelectArea = nil;
        [self calculationCityAreaArr];
        [pickerView selectRow:[self.cityArr indexOfObject:self.currentSelectCity] inComponent:1 animated:YES];
        [pickerView selectRow:[self.areaArr indexOfObject:self.currentSelectArea] inComponent:2 animated:YES];
    }else if (component == 1) {
        self.currentSelectCity = self.cityArr[row];
        self.currentSelectArea = nil;
        [self calculationCityAreaArr];
        [pickerView selectRow:[self.areaArr indexOfObject:self.currentSelectArea] inComponent:2 animated:YES];
    }else {
        self.currentSelectArea = self.areaArr[row];
    }
    
}

- (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view {
    if (!view) {
        view = [[UIView alloc] init];
    }
    UILabel *lblTitle = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, (kScreenWidth - 50) / 3, 30)];
    lblTitle.textAlignment = NSTextAlignmentCenter;
    lblTitle.font = [UIFont systemFontOfSize:15];
    lblTitle.textColor = HmColorRGB(51, 51, 51);
    if (component == 0) {
        lblTitle.text = self.provinceArr[row];
    }else if (component == 1) {
        lblTitle.text = self.cityArr[row];
    }else {
        lblTitle.text = self.areaArr[row];
    }
    [view addSubview:lblTitle];
    return view;
}

6. 接下来就是实现取消和确定按钮的点击时间咯,当然还有点击半透明背景的事件(背景因为有点击的功效,不想麻烦再添加手势,就直接使用button来作为背景啦)~~~~~我很懒的

/// 取消
- (void)cancelAction:(UIButton *)btn {
    [self removeFromSuperview];
}

/// 确定
- (void)sureAction:(UIButton *)btn {
    // 这里使用了block来返回选中的省市区数据
    if (self.confirmSelect) {
        self.confirmSelect(@[self.currentSelectProvince, self.currentSelectCity, self.currentSelectArea]);
    }
    [self removeFromSuperview];
}

7. 还有一个展示的方法,虽然只有一句话,但也给封成方法了。

- (void)show {
    [[[UIApplication sharedApplication] keyWindow] addSubview:self];
}

8. 在自定义view.h文件中只有简单的三行代码

@property (nonatomic, copy) void (^confirmSelect)(NSArray *address);
- (instancetype)initWithLastContent:(NSArray *)lastContent;
- (void)show;

9. 最后就是使用咯 超级简单的啦

/// 这里传进去的self. currentProvince 等等的都是本页面的村储值
HmSelectAdView *selectV = [[HmSelectAdView alloc] initWithLastContent:self.currentProvince ? @[self.currentProvince, self.currentCity, self.currentArea] : nil];
    selectV.confirmSelect = ^(NSArray *address) {
        self.currentProvince = address[0];
        self.currentCity = address[1];
        self.currentArea = address[2];
        self.lblSelectAd.text = [NSString stringWithFormat:@"%@ %@ %@", self.currentProvince, self.currentCity, self.currentArea];
    };
    [selectV show];
是不是超级简单的啦,最后说一句哈,不要说我的省市区数组没有初始化,我使用的是懒加载,没有粘上代码 哈哈哈哈哈

感觉有用就收藏一下吧 顺便给个免费的喜欢😁😁😁 如果需要省市区json数据可以问我要,也可以上网百度(没有时间把这些整理处理,就暂时这样咯,等什么时候整理出来了直接上传到github,然后把链接贴在这里)不懂得也可以问我哟

临时整理了一个小demo 有需要的可以下载看看
代码传送门

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

推荐阅读更多精彩内容