iOS开发-实现地区选择三级联动
前言:对于地区的选择使用的地方还是比较多的,而地区选择的使用一般都是伴随着联动,常用的二级和三级的联动效果比较多一些。今天小编给大家主要介绍一下地区选择三级联动的实现。相信大家会了三级的联动效果,那么二级联动就是信手拈来了。
原理:使用代理传值。地区选择的菜单栏和展示选择区分开定制,同时通过代理方法将值和事件传递并进行相应的view
刷新。如果有兴趣的朋友还可以把这两个view
合并到一个新的整体view
中进行再次封装。
首先,大家先来看一下效果图
下面,我们先来创建地区选择的菜单栏的
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
类就可以了。为了使数据看起来更整洁,我简单写了一个城市地区的plis
t文件,里边简单存了几个城市。
#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
最后,希望能够帮助到有需要的猿友们,如果有哪里不清楚的地方或者建议,请提出,小编会在看到的第一时间给你回复。愿我们能够共同成长进步,在开发的道路上越走越远!谢谢!