Osheep

时光不回头,当下最重要。

撸个iOS三级联动选择器

无聊ing…封装个省市区三级联动选择器的小demo吧。
上家公司的三级地区选择器的数据是一次性通过网络请求就能获取到的,但新东家这边并不是,而是先选择了省获取省的Id再去获取市,再通过得到市的Id获取区域,show code之前,先看下需要考虑的几个点:

1)怎么设置默认值,关键代码
[self.pickerView selectRow:xxx inComponent:xxx animated:YES];
2)怎么让三级之间联动 ,关键代码
[self pickerView:self.pickerView didSelectRow:0 inComponent:0 ];//联动轮子1  必须得本轮有数据后触发否则crash
3) 防崩溃的点

—————————- 分割线 ————————————-

先看下效果图

《撸个iOS三级联动选择器》

最终效果.jpg

关于设置默认值,三级联动,用UIPickView的话就是有3个轮子(component),首先我们要想到,第一次向后台发起请求,我们只能获取到第0个component的数据,只有当你滚动轮子的时候才会获取到省的Id发起请求来获得该省的市的数据,也就是第1个component的数据,依此类推,滚动第1个component发起请求来获取第2个component的数据,因此,pickView的监听轮子滚动的代理起了重要作用

- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component;

我们通过接口获取第0个component的数据,这边是后台规定的使用id=0,获取到以后,默认选中第0个component的第0个row并主动调用触发pick的轮子滚动代理来联动第1个component【要在获取数据成功后再执行这部操作,因此放在数据请求成功的回调内】,代码为

 [self pickerView:self.pickerView didSelectRow:0 inComponent:0 ];

在各轮子滚动过程中,用一个中间值

_selectedRow0记录下第0个component的选中行
_selectedRow1记录下第1个component的选中行
_selectedRow2记录下第2个component的选中行,

这里需要记住,滚动某个轮子只能对它后面的轮子产生影响,所以当滚动第0个component的时候使_selectedRow1,_selectedRow2均置为0,这里注意,上面提到的

默认选中第0个component的第0个row并主动调用触发pick的轮子滚动代理来联动第1个component
要先将轮子上的数据渲染好,设置好默认值才能主动调用监听轮子滚动的代理,否则会导致崩溃,另一个防崩溃的点如下图
《撸个iOS三级联动选择器》

第三级没数据.jpg
发现第三级没数据的时候,如果你在代码里没加【安全措施】,那也会导致崩溃,要在请求到第三级的数据后做下判断,如果个数为空,将该级对应的数据源置为nil。(其它两级的轮子最好也加判断)

最后,由于这是个封装的类,最终要得到选中的详细信息,可通过代理或block传值给controller。

又是你们最喜欢show code环节:
.h文件
#import <UIKit/UIKit.h>

//定制代理协议
@protocol ZLMAddressPickerViewDelegate <NSObject>

- (void)addressPickerViewDidSelected:(NSString *)areaName;//点击上方完成按钮的代理传回拼接好的选中地址

- (void)addressPickerViewDidClose;//点击关闭代理

@end

@interface ZLMAddressPickerView : UIView

@property (weak, nonatomic) id<ZLMAddressPickerViewDelegate> delegate;

@end
.m文件
#import "ZLMAddressPickerView.h"
#import "AFHttpUtils.h"
#import "AreaModel.h"
@interface ZLMAddressPickerView () <UIPickerViewDataSource, UIPickerViewDelegate>
@property (strong, nonatomic) UIPickerView *pickerView;
@property (strong, nonatomic) AreaModel    *provBridge;
@property (strong, nonatomic) AreaModel    *cityBridge;
@property (strong, nonatomic) AreaModel    *areaBridge;
@property (copy, nonatomic) NSArray<Area *> * provDataArr;//省数组
@property (copy, nonatomic) NSArray<Area *> * cityDataArr;//市数组
@property (copy, nonatomic) NSArray<Area *>  * areaDataArr;//区数组
@end

@implementation ZLMAddressPickerView
{
    NSInteger _selectRow0;//记录第0个轮子的选择行
    NSInteger _selectRow1;
    NSInteger _selectRow2;
    NSString *_areaString;//最后要传回的详细地址拼接字符串
    Area *_proModel;//记录下选中省的数据
    Area *_cityModel;
    Area *_areaModel;

}

- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self setup];
    }
    return self;
}

- (void)setup {
  
    _selectRow0 = 0;
    _selectRow1 = 0;
    _selectRow2 = 0;

    self.backgroundColor    = [UIColor whiteColor];
    UIToolbar *toolbar      = [[UIToolbar alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.bounds), 44)];
    toolbar.backgroundColor = [UIColor whiteColor];
    [self addSubview:toolbar];
    
    UIBarButtonItem *closeItem      = [[UIBarButtonItem alloc] initWithTitle:@"关闭" style:UIBarButtonItemStylePlain target:self action:@selector(selectAddressClose)];
    UIBarButtonItem *completeItem   = [[UIBarButtonItem alloc] initWithTitle:@"完成" style:UIBarButtonItemStylePlain target:self action:@selector(selectAddressComplete)];
    UIBarButtonItem *spaceItem      = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
    toolbar.items                                          = @[closeItem, spaceItem, completeItem];
    
    self.pickerView.frame = CGRectMake(0, 44, CGRectGetWidth(self.bounds), CGRectGetHeight(self.bounds) - 44);

    [self addSubview:self.pickerView];
    
    [self downloadProv];
    
}

#pragma mark - http methods

/*省*/
- (void)downloadProv {
    
    NSMutableDictionary  *dic = [NSMutableDictionary dictionaryWithDictionary:  @{@"id":@(0)} ];
    
    [AFHttpUtils sendPostTaskWithUrl:[NSString stringWithFormat:@"%@/app/member/area",BASE_DOMAIN_URL] paramenters:dic successHandle:^(NSURLSessionDataTask *task, id responseObject) {
        
         NSLog(@"PROV:%@",responseObject);
        
        self.provBridge = [AreaModel mj_objectWithKeyValues:responseObject];
       
        if (self.provBridge.error_code==0) {
            
            self.provDataArr=self.provBridge.data;
            
             [self pickerView:self.pickerView didSelectRow:0 inComponent:0 ];//联动轮子1 必须得本轮有数据后才能触发didselect
            
            [self.pickerView reloadAllComponents];
    
        }
    } errorHandle:^(NSError *error) {
        
    }];

}
/*市*/
- (void)downloadCityWithId:(NSInteger)provId {
    
    NSMutableDictionary  *dic = [NSMutableDictionary dictionaryWithDictionary:  @{@"id":@(provId)} ];
    
    [AFHttpUtils sendPostTaskWithUrl:[NSString stringWithFormat:@"%@/app/member/area",BASE_DOMAIN_URL] paramenters:dic successHandle:^(NSURLSessionDataTask *task, id responseObject) {
        
        NSLog(@"CITY:%@",responseObject);
        
        self.cityBridge = [AreaModel mj_objectWithKeyValues:responseObject];
    
        if (self.cityBridge.error_code==0) {
            
            self.cityDataArr=self.cityBridge.data;
            
            [self.pickerView reloadComponent:1];
            
            [self.pickerView selectRow:0 inComponent:1 animated:YES];//默认选择row0
            
            [self pickerView:self.pickerView didSelectRow:0 inComponent:1 ];//联动轮子2 必须得本轮有数据后才能触发didselect
            
            _cityModel = self.cityDataArr[_selectRow1];
            
            [self downloadAreaWithId:_cityModel.area_id];
            
        }
    } errorHandle:^(NSError *error) {
        
    }];
    
}
/*区*/
- (void)downloadAreaWithId:(NSInteger)cityId {
    
    NSMutableDictionary  *dic = [NSMutableDictionary dictionaryWithDictionary:  @{@"id":@(cityId)} ];
    
    [AFHttpUtils sendPostTaskWithUrl:[NSString stringWithFormat:@"%@/app/member/area",BASE_DOMAIN_URL] paramenters:dic successHandle:^(NSURLSessionDataTask *task, id responseObject) {
        
        NSLog(@"AREA:%@",responseObject);
        
        self.areaBridge = [AreaModel mj_objectWithKeyValues:responseObject];
        
        if (self.areaBridge.error_code==0&&self.areaBridge.data.count>0) {
            
            self.areaDataArr=self.areaBridge.data;
            
        }else{
           
            self.areaDataArr=nil;
            
        }
        [self.pickerView reloadComponent:2];
        
        [self.pickerView selectRow:0 inComponent:2 animated:YES];
        
        [self pickerView:self.pickerView didSelectRow:0 inComponent:2 ];
   
    } errorHandle:^(NSError *error) {
        
    }];
    
}
#pragma mark - events response
- (void)selectAddressComplete {
    [self.delegate addressPickerViewDidSelected:_areaString];
}

- (void)selectAddressClose {
    [self.delegate addressPickerViewDidClose];
}

#pragma mark - UIPickerViewDataSource

//确定picker的轮子个数
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
    
    return 3;
}

//确定picker的每个轮子的item数
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
    if (component==0) {
         return self.provDataArr.count;
        
    }else if(component==1){
       return self.cityDataArr.count;
        
    }else{
        return self.areaDataArr.count;
        
    }
}
- (CGFloat)pickerView:(UIPickerView *)pickerView rowHeightForComponent:(NSInteger)component{
    return 36;
}

//确定每个轮子的每一项显示什么内容
- (NSAttributedString *)pickerView:(UIPickerView *)pickerView attributedTitleForRow:(NSInteger)row forComponent:(NSInteger)component{
    
    NSDictionary * attrDic = @{NSForegroundColorAttributeName:[UIColor blackColor],
                               NSFontAttributeName:[UIFont systemFontOfSize:12]};
    Area *area;
    if (component==0) {
       area = self.provDataArr[row];
        
    }else if(component==1){
        area = self.cityDataArr[row];
       
    }else{
        area = self.areaDataArr[row];
     
    }
     NSAttributedString * attrString = [[NSAttributedString alloc] initWithString:area.name
                                                 attributes:attrDic];
    return attrString;
}

//监听轮子的移动
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {
    
    if (component==0) {
        
        _selectRow0 = [pickerView selectedRowInComponent:0];
        
        _selectRow1 = 0;
        
        _selectRow2 = 0;
        
        _proModel   = self.provDataArr[_selectRow0];
        
        [self downloadCityWithId:_proModel.area_id];
            
    }else if(component==1){
       
        _selectRow1 = [pickerView    selectedRowInComponent:1];
        
        _selectRow2 = 0;
        
        _cityModel  = self.cityDataArr[_selectRow1];
        
         [self downloadAreaWithId:_cityModel.area_id];
       
    }else{
        
       _selectRow2 = [pickerView selectedRowInComponent:2];

        if (self.areaDataArr&&self.areaDataArr.count>0) {

              _areaModel = self.areaDataArr[_selectRow2];
        }else{
            _areaModel = nil;
        }
    }
     if(_areaModel==nil){
       _areaString = [NSString stringWithFormat:@"%@ %@",_proModel.name,_cityModel.name];
     }else{
     _areaString = [NSString stringWithFormat:@"%@ %@ %@",_proModel.name,_cityModel.name,_areaModel.name];
     }
}

#pragma mark - getters and setters
- (UIPickerView *)pickerView {
    if (_pickerView == nil) {
        _pickerView = [[UIPickerView alloc] init];
        _pickerView.delegate   = self;
        _pickerView.dataSource = self;
        
    }
    return _pickerView;
}

@end

最后在controller中调用

(1)导入

#import "ZLMAddressPickerView.h"

(2)定义一个对象并遵守代理协议

@property (strong, nonatomic) ZLMAddressPickerView *addressPickerView;

(3)懒加载生成对象(个人习惯)

- (ZLMAddressPickerView *)addressPickerView {
    if (!_addressPickerView) {
        _addressPickerView          = [[ZLMAddressPickerView alloc] initWithFrame:CGRectMake(0, SCREEN_HEIGHT-244-64, SCREEN_WIDTH, 244)];
        _addressPickerView.delegate = self;
    }
    return _addressPickerView;
}

(4)在点击跳出三级联动选择器的地方

 [self.view addSubview:self.addressPickerView];

(5)别忘了实现代理

#pragma mark - ZLMAddressPickerViewDelegate

- (void)addressPickerViewDidSelected:(NSString *)areaName {

    self.areaLabel.text = areaName;//将传回的详细地址字符串赋值

    [self addressPickerViewDidClose];
}

- (void)addressPickerViewDidClose {

    [self.addressPickerView removeFromSuperview];
}

以上。

点赞