iOS开发技能iOS技术分享程序员

iOS开发-实现地区选择三级联动

2017-03-29  本文已影响335人  it_hao528
Map.png

前言:对于地区的选择使用的地方还是比较多的,而地区选择的使用一般都是伴随着联动,常用的二级和三级的联动效果比较多一些。今天小编给大家主要介绍一下地区选择三级联动的实现。相信大家会了三级的联动效果,那么二级联动就是信手拈来了。

原理:使用代理传值。地区选择的菜单栏和展示选择区分开定制,同时通过代理方法将值和事件传递并进行相应的view刷新。如果有兴趣的朋友还可以把这两个view合并到一个新的整体view中进行再次封装。

首先,大家先来看一下效果图

地区选择三级联动.gif
下面,我们先来创建地区选择的菜单栏的view.h如下:
#import <UIKit/UIKit.h>

@protocol PickPlaceMenuDelegate;
@interface PickPlaceMenu : UIView

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

/**
 配置菜单

 @param province 省
 @param city     市
 @param district 区/县
 */
- (void)configViewWithProvince:(NSString *)province city:(NSString *)city district:(NSString *)district;

/**
 重置菜单
 */
- (void)reset;
@end
@protocol PickPlaceMenuDelegate <NSObject>

//代理方法
@optional
/**
 点击菜单

 @param menuView 菜单view
 @param index    选中菜单的下标:0代表省,1代表市,2代表区/县
 @param isShow   是否弹出显示当前的对应列表
 */
- (void)menuView:(PickPlaceMenu *)menuView didSelectAtIndex:(NSInteger)index isShow:(BOOL)isShow;
@end

所有方法和代理都给大家注释了,这里不再多说,接下来我们来看在.m中的布局及实现如下:

#import "PickPlaceMenu.h"

@interface PickPlaceMenu ()

@property (nonatomic, strong) UIButton * btn_province;
@property (nonatomic, strong) UIButton * btn_city;
@property (nonatomic, strong) UIButton * btn_district;
@property (nonatomic, strong) UILabel * lab_lineV1;
@property (nonatomic, strong) UILabel * lab_lineV2;
@property (nonatomic, strong) UILabel * lab_lineH;
@end

@implementation PickPlaceMenu

- (instancetype)initWithFrame:(CGRect)frame {
    
    self = [super initWithFrame:frame];
    if (self) {
        
        self.backgroundColor = [UIColor whiteColor];
        [self addSubviews];
    }
    return self;
}

#pragma mark - add subviews

- (void)addSubviews {
    
    [self addSubview:self.btn_province];
    [self addSubview:self.btn_city];
    [self addSubview:self.btn_district];
    [self addSubview:self.lab_lineV1];
    [self addSubview:self.lab_lineV2];
    [self addSubview:self.lab_lineH];
}

- (void)layoutSubviews {
    [super layoutSubviews];
    
    float width = self.bounds.size.width;
    [_btn_province mas_makeConstraints:^(MASConstraintMaker *make) {
        
        make.width.mas_equalTo(@((width - 2)/3));
        make.top.mas_equalTo(@0);
        make.bottom.mas_equalTo(@-1);
        make.left.mas_equalTo(@0);
    }];
    
    [_lab_lineV1 mas_makeConstraints:^(MASConstraintMaker *make) {
        
        make.width.mas_equalTo(@1);
        make.top.mas_equalTo(@5);
        make.bottom.mas_equalTo(@-6);
        make.left.mas_equalTo(_btn_province.mas_right);
    }];
    
    [_btn_city mas_makeConstraints:^(MASConstraintMaker *make) {
        
        make.width.mas_equalTo(@((width - 2)/3));
        make.top.mas_equalTo(@0);
        make.bottom.mas_equalTo(@-1);
        make.left.mas_equalTo(_lab_lineV1.mas_right);
    }];
    
    [_lab_lineV2 mas_makeConstraints:^(MASConstraintMaker *make) {
        
        make.width.mas_equalTo(@1);
        make.top.mas_equalTo(@5);
        make.bottom.mas_equalTo(@-6);
        make.left.mas_equalTo(_btn_city.mas_right);
    }];
    
    [_btn_district mas_makeConstraints:^(MASConstraintMaker *make) {
        
        make.width.mas_equalTo(@((width - 2)/3));
        make.top.mas_equalTo(@0);
        make.bottom.mas_equalTo(@-1);
        make.left.mas_equalTo(_lab_lineV2.mas_right);
    }];
    
    [_lab_lineH mas_makeConstraints:^(MASConstraintMaker *make) {
        
        make.size.mas_equalTo(CGSizeMake(Screen_Width, 1));
        make.left.mas_equalTo(@0);
        make.bottom.mas_equalTo(@0);
    }];
}

#pragma mark - config view

- (void)configViewWithProvince:(NSString *)province city:(NSString *)city district:(NSString *)district {
    
    if (province) {
        
        [_btn_province setTitle:province forState:UIControlStateNormal];
    }
    
    if (city) {
        
        [_btn_city setTitle:city forState:UIControlStateNormal];
    }
    
    if (district) {
        
        [_btn_district setTitle:district forState:UIControlStateNormal];
    }
}

- (void)reset {
    
    if (_btn_province.selected) {
        
        _btn_province.selected = NO;
    }
    
    if (_btn_city.selected) {
        
        _btn_city.selected = NO;
    }
    
    if (_btn_district.selected) {
        
        _btn_district.selected = NO;
    }
}

#pragma mark - button event

- (void)clickSelectProvince:(UIButton *)sender {
    
    sender.selected = !sender.selected;
    _btn_city.selected = NO;
    _btn_district.selected = NO;
    
    if ([self.delegate respondsToSelector:@selector(menuView:didSelectAtIndex:isShow:)]) {
        
        [self.delegate menuView:self didSelectAtIndex:0 isShow:sender.selected];
    }
}

- (void)clickSelectCity:(UIButton *)sender {
    
    sender.selected = !sender.selected;
    _btn_province.selected = NO;
    _btn_district.selected = NO;
    
    if ([self.delegate respondsToSelector:@selector(menuView:didSelectAtIndex:isShow:)]) {
        
        [self.delegate menuView:self didSelectAtIndex:1 isShow:sender.selected];
    }
}

- (void)clickSelectDistrict:(UIButton *)sender {
    
    sender.selected = !sender.selected;
    _btn_province.selected = NO;
    _btn_city.selected = NO;
    
    if ([self.delegate respondsToSelector:@selector(menuView:didSelectAtIndex:isShow:)]) {
        
        [self.delegate menuView:self didSelectAtIndex:2 isShow:sender.selected];
    }
}

#pragma mark - setter and getter

- (UIButton *)btn_province {
    
    if (!_btn_province) {
        
        _btn_province = [[UIButton alloc] init];
        _btn_province.titleLabel.font = [UIFont systemFontOfSize:15];
        [_btn_province setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
        [_btn_province setTitleColor:[UIColor orangeColor] forState:UIControlStateSelected];
        [_btn_province addTarget:self action:@selector(clickSelectProvince:) forControlEvents:UIControlEventTouchUpInside];
    }
    return _btn_province;
}

- (UIButton *)btn_city {
    
    if (!_btn_city) {
        
        _btn_city = [[UIButton alloc] init];
        _btn_city.titleLabel.font = [UIFont systemFontOfSize:15];
        [_btn_city setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
        [_btn_city setTitleColor:[UIColor orangeColor] forState:UIControlStateSelected];
        [_btn_city addTarget:self action:@selector(clickSelectCity:) forControlEvents:UIControlEventTouchUpInside];
    }
    return _btn_city;
}

- (UIButton *)btn_district {
    
    if (!_btn_district) {
        
        _btn_district = [[UIButton alloc] init];
        _btn_district.titleLabel.font = [UIFont systemFontOfSize:15];
        [_btn_district setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
        [_btn_district setTitleColor:[UIColor orangeColor] forState:UIControlStateSelected];
        [_btn_district addTarget:self action:@selector(clickSelectDistrict:) forControlEvents:UIControlEventTouchUpInside];
    }
    return _btn_district;
}

- (UILabel *)lab_lineV1 {
    
    if (!_lab_lineV1) {
        
        _lab_lineV1 = [[UILabel alloc] init];
        _lab_lineV1.backgroundColor = Color_Line;
    }
    return _lab_lineV1;
}

- (UILabel *)lab_lineV2 {
    
    if (!_lab_lineV2) {
        
        _lab_lineV2 = [[UILabel alloc] init];
        _lab_lineV2.backgroundColor = Color_Line;
    }
    return _lab_lineV2;
}

- (UILabel *)lab_lineH {
    
    if (!_lab_lineH) {
        
        _lab_lineH = [[UILabel alloc] init];
        _lab_lineH.backgroundColor = Color_Line;
    }
    return _lab_lineH;
}
@end

大家可以看到,这里是用了三个按钮来实现的菜单点击效果,这个按钮和布局,大家可以根据自己的需求进行相应的修改,不过事件的处理和代理的赋值基本是不需要修改的。

接下来,我们继续创建展示地区选择列表的一个view.h如下:

#import <UIKit/UIKit.h>

@protocol PickPlaceViewDelegate;
@interface PickPlaceView : UIView

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

/**
 刷新展示地区的列表view

 @param dataArr   数据源:省/市/ 区、县
 @param component 菜单选中下标(这里我称之为列)
 */
- (void)reloadViewWithData:(NSArray *)dataArr component:(NSInteger)component;

/**
 收起列表
 */
- (void)hide;
@end
@protocol PickPlaceViewDelegate <NSObject>

// 代理方法
@optional

/**
 选中地区

 @param placeView 地区view
 @param place     选中的地区
 @param component 在菜单的第几个(列)
 @param row       选中的在第几行
 */
- (void)placeView:(PickPlaceView *)placeView didSelectPlace:(NSString *)place atComponent:(NSInteger)component atRow:(NSInteger)row;

/**
 展示地区view收起
 */
- (void)placeViewDidHide;

@end

这里也都添加了注释,相信大家都能够看的明白。我们继续来看在.m中的实现如下:

#import "PickPlaceView.h"

#define Cell_Place    @"Cell_Place"
@interface PickPlaceView () <UITableViewDelegate, UITableViewDataSource>

@property (nonatomic, strong) UITableView * tableView;

@property (nonatomic, strong) NSMutableArray * dataArr;
@property (nonatomic, assign) NSInteger index;

@property (nonatomic, assign) float kWidth;
@property (nonatomic, assign) float kHeight;
@end
@implementation PickPlaceView

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

#pragma mark - add subviews

- (void)addSubviews {
    
    [self addSubview:self.tableView];
}

#pragma mark - layout subviews

- (void)layoutSubviews {
    [super layoutSubviews];
    
    _kWidth = self.bounds.size.width;
    _kHeight = self.bounds.size.height;
}

#pragma mark - public method

- (void)reloadViewWithData:(NSArray *)dataArr component:(NSInteger)component {
    
    _index = component;
    self.dataArr = [NSMutableArray arrayWithArray:dataArr];
    
    float height = self.dataArr.count * 44;
    if (_kHeight - height > 0) {
        
        _tableView.frame = CGRectMake(0, -height, _kWidth, height);
    } else {
        
        _tableView.frame = CGRectMake(0, -_kHeight, _kWidth, _kHeight);
    }
    [_tableView reloadData];
    
    [self show];
}

#pragma mark - touch event

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    if ([self.delegate respondsToSelector:@selector(placeViewDidHide)]) {
        
        [self.delegate placeViewDidHide];
    }
    [self hide];
}

#pragma mark - tableView dataSource and delegate

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    
    return self.dataArr.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    
    UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:Cell_Place forIndexPath:indexPath];
    
    if (indexPath.row < self.dataArr.count) {
        
        cell.textLabel.font = [UIFont systemFontOfSize:15];
        cell.textLabel.text = _dataArr[indexPath.row];
    }
    return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    
    NSString * place = self.dataArr[indexPath.row];
    
    if ([self.delegate respondsToSelector:@selector(placeView:didSelectPlace:atComponent:atRow:)]) {
        
        [self.delegate placeView:self didSelectPlace:place atComponent:_index atRow:indexPath.row];
    }
    [self hide];
}

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    
    float offset = scrollView.contentOffset.y;
    
    if (offset <= 0) {
        
        [scrollView setContentOffset:CGPointMake(0, 0)];
    }
}

#pragma mark - private method

- (void)show {
    
    [UIView animateWithDuration:0.25 animations:^{
        
        self.backgroundColor = Color_RGB_Alpha(0, 0, 0, 0.3);
        _tableView.frame = CGRectMake(0, 0, _kWidth, _tableView.bounds.size.height);
    }];
}

- (void)hide {
    
    [UIView animateWithDuration:0.15 animations:^{
        
        self.backgroundColor = Color_RGB_Alpha(0, 0, 0, 0);
        _tableView.frame = CGRectMake(0, -_kHeight, _kWidth, _kHeight);
    } completion:^(BOOL finished) {
        
        self.hidden = YES;
    }];
}

#pragma mark - setter and getter

- (UITableView *)tableView {
    
    if (!_tableView) {
        
        _tableView = [[UITableView alloc] init];
        _tableView.delegate = self;
        _tableView.dataSource = self;
        _tableView.backgroundColor = Color_RGB_Alpha(0, 0, 0, 0);
        [_tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:Cell_Place];
        _tableView.tableFooterView = [[UIView alloc] init];
    }
    return _tableView;
}

- (NSMutableArray *)dataArr {
    
    if (!_dataArr) {
        
        _dataArr = [[NSMutableArray alloc] init];
    }
    return _dataArr;
}
@end

这里使用了UITableView来进行列表的展示,并通过动画来实现向下弹出和向上收起的动画效果及半透明背景的渐变效果。每次调用外漏刷新数据方法时,列表刷新数据并向下弹出,选中或者点击空白地方时向上收起,同时回调该事件。以让菜单栏也进行相应的view的刷新。

封装基本完成,下面我们来简单看一下使用方法。
这里,我建了一个Header类才存储所有的头文件,使用时只需要导入该Header类就可以了。为了使数据看起来更整洁,我简单写了一个城市地区的plist文件,里边简单存了几个城市。

#import "ViewController.h"
#import "PickPlace.h"

@interface ViewController () <PickPlaceMenuDelegate, PickPlaceViewDelegate>

@property (nonatomic, strong) PickPlaceMenu * view_menu;
@property (nonatomic, strong) PickPlaceView * view_place;

@property (nonatomic, strong) NSMutableArray * placeArr;

@property (nonatomic, assign) NSInteger provinceIndex;
@property (nonatomic, assign) NSInteger cityIndex;
@property (nonatomic, assign) NSInteger districtIndex;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.navigationItem.title = @"地区选择";
    
    [self loadData];
    [self addSubviews];
    [self makeConstraintsForUI];
}

#pragma mark - load data

- (void)loadData {
    
    _provinceIndex = 0;
    _cityIndex = 0;
    _districtIndex = 0;
    
    NSDictionary * dic = [NSDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"Place" ofType:@"plist"]];
    NSLog(@"%@", dic);
    NSArray * provinceArr = dic[@"province"];
    
    for (int i = 0; i < provinceArr.count; i++) {
        
        NSDictionary * provinceDic = provinceArr[i];
        Province * province = [[Province alloc] init];
        province.province = provinceDic[@"province"];
        
        NSArray * cityArr = provinceDic[@"city"];
        for (int j = 0; j < cityArr.count; j++) {
            
            NSDictionary * cityDic = cityArr[j];
            City * city = [[City alloc] init];
            city.city = cityDic[@"city"];
            
            NSArray * districtArr = cityDic[@"district"];
            for (int k = 0; k < districtArr.count; k++) {
                
                District * district = [[District alloc] init];
                district.district = districtArr[k];
                [city.districtArr addObject:district];
            }
            [province.cityArr addObject:city];
        }
        
        [self.placeArr addObject:province];
    }
    
    Province * province = _placeArr.firstObject;
    City * city = province.cityArr.firstObject;
    District * district = city.districtArr.firstObject;
    
    [self.view_menu configViewWithProvince:province.province city:city.city district:district.district];
}

#pragma mark - add subviews

- (void)addSubviews {
    
    [self.view addSubview:self.view_place];
    [self.view addSubview:self.view_menu];
    _view_place.hidden = YES;
}

#pragma mark - make constraints

- (void)makeConstraintsForUI {
    
    [_view_menu mas_makeConstraints:^(MASConstraintMaker *make) {
        
        make.height.mas_equalTo(@50);
        make.left.mas_equalTo(@0);
        make.right.mas_equalTo(@0);
        make.top.mas_equalTo(@64);
    }];
    
    [_view_place mas_makeConstraints:^(MASConstraintMaker *make) {
        
        make.left.mas_equalTo(@0);
        make.top.mas_equalTo(_view_menu.mas_bottom);
        make.bottom.mas_equalTo(@0);
        make.right.mas_equalTo(@0);
    }];
}

#pragma mark - pickView delegate

- (void)menuView:(PickPlaceMenu *)menuView didSelectAtIndex:(NSInteger)index isShow:(BOOL)isShow {
    
    if (index == 0) {
        
        if (isShow) {
            
            _view_place.hidden = NO;
            
            NSMutableArray * provinceArr = [[NSMutableArray alloc] init];
            for (int i = 0; i < self.placeArr.count; i++) {
                
                Province * province = _placeArr[i];
                [provinceArr addObject:province.province];
            }
            [_view_place reloadViewWithData:provinceArr component:0];
        } else {
            
            [_view_place hide];
        }
    } else if (index == 1) {
        
        if (isShow) {
            
            _view_place.hidden = NO;
            Province * province = _placeArr[_provinceIndex];
            NSMutableArray * cityArr = [[NSMutableArray alloc] init];
            for (int i = 0; i < province.cityArr.count; i++) {
                
                City * city = province.cityArr[i];
                [cityArr addObject:city.city];
            }
            [_view_place reloadViewWithData:cityArr component:1];
        } else {
            
            [_view_place hide];
        }
    } else {
        
        if (isShow) {
            
            _view_place.hidden = NO;
            Province * province = _placeArr[_provinceIndex];
            City * city = province.cityArr[_cityIndex];
            NSMutableArray * districtArr = [[NSMutableArray alloc] init];
            for (int i = 0; i < city.districtArr.count; i++) {
                
                District * district = city.districtArr[i];
                [districtArr addObject:district.district];
            }
            [_view_place reloadViewWithData:districtArr component:2];
        } else {
            
            [_view_place hide];
        }
    }
}

#pragma mark - placeView delegate

- (void)placeView:(PickPlaceView *)placeView didSelectPlace:(NSString *)place atComponent:(NSInteger)component atRow:(NSInteger)row {
    
    if (component == 0) {
        
        if (_provinceIndex != row) {
            
            _provinceIndex = row;
            _cityIndex = 0;
            _districtIndex = 0;
            Province * province = _placeArr[row];
            City * city = province.cityArr.firstObject;
            District * district = city.districtArr.firstObject;
            [_view_menu configViewWithProvince:place city:city.city district:district.district];
        }
    } else if (component == 1) {
        
        if (_cityIndex != row) {
            
            _cityIndex = row;
            _districtIndex = 0;
            Province * province = _placeArr[_provinceIndex];
            City * city = province.cityArr[row];
            District * district = city.districtArr.firstObject;
            [_view_menu configViewWithProvince:nil city:place district:district.district];
        }
    } else {
        
        if (_districtIndex != row) {
            
            _districtIndex = row;
            [_view_menu configViewWithProvince:nil city:nil district:place];
        }
    }
    
    [_view_menu reset];
}

- (void)placeViewDidHide {
    
    [_view_menu reset];
}

#pragma mark - setter and getter

- (PickPlaceMenu *)view_menu {
    
    if (!_view_menu) {
        
        _view_menu = [[PickPlaceMenu alloc] init];
        _view_menu.delegate = self;
    }
    return _view_menu;
}

- (PickPlaceView *)view_place {
    
    if (!_view_place) {
        
        _view_place = [[PickPlaceView alloc] init];
        _view_place.delegate = self;
    }
    return _view_place;
}

- (NSMutableArray *)placeArr {
    
    if (!_placeArr) {
        
        _placeArr = [[NSMutableArray alloc] init];
    }
    return _placeArr;
}
@end

从使用上,可以清楚的看出,主要是在两个定制的view的代理方法中进行相互的刷新view操作,同时在代理方法中可以获取到我们需求的信息,即选择地区是属于哪一级的,以及每一级的当前选择是什么或者说每一级当前选择展示的id是什么。
这里也简单的将省/市/区、县做成了三级关联的model,这个大家可以根据需求自己创建。

demo下载地址:https://github.com/guorenhao/PickPlace

最后,希望能够帮助到有需要的猿友们,如果有哪里不清楚的地方或者建议,请提出,小编会在看到的第一时间给你回复。愿我们能够共同成长进步,在开发的道路上越走越远!谢谢!

上一篇下一篇

猜你喜欢

热点阅读