一、简介
1. 继承关系
UIPickerView是UIView的子类。类似的控件是UIDatePicker,但它是UIControl的子类,UIControl又是UIView的子类,只能说它们有相同的祖父类。从表面上看两者好像有直接的继承关系,但是实质不是。
2. 使用场景
UIPickerView使用频率不高,通常使用在注册模块,当用户需要选择一些东西的时候,比如说城市,往往弹出一个PickerView给他们选择
3. 基本概念
Component:称组或列,表示能滚动的列
row:行,表示当前选中的行
dataSource:数据源,给UIPickerView设置数据源,就可以显示数据,但是只实现数据源协议方法,每一行会以问号?显示,并不能显示指定数据;要设置数据,要设置代理和实现代理协议方法
delegate:代理,给UIPickerView设置代理,并实现了代理协议,可以设置显示的数据,监听UIPickerView的操作
二、常见属性
// 数据源
@property(nonatomic,assign) id<UIPickerViewDataSource> dataSource;
// 代理
@property(nonatomic,assign) id<UIPickerViewDelegate> delegate;
// 只读,在数据源或代理中获取总列数
@property(nonatomic,readonly) NSInteger numberOfComponents;
三、常见方法
// 获取第component列有多少行
- (NSInteger)numberOfRowsInComponent:(NSInteger)component;
// 获取第component列的行的尺寸
- (CGSize)rowSizeForComponent:(NSInteger)component;
// 获取第component列第row行的视图,前提是该列必须是通过视图显示
- (UIView *)viewForRow:(NSInteger)row forComponent:(NSInteger)component;
// 刷新所有列的数据
- (void)reloadAllComponents;
// 刷新第component列的数据
- (void)reloadComponent:(NSInteger)component;
// 在PickerView里显示选中第component列第row的数据
- (void)selectRow:(NSInteger)row inComponent:(NSInteger)component animated:(BOOL)animated;
// 获取第component列选中的行号
- (NSInteger)selectedRowInComponent:(NSInteger)component;
四、数据源
1. 设置数据源的两种方式:a.拖线 b.代码
2. 数据源协议方法
// 设置PickerView有多列,必选
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView;
// 设置PickerView第component列的行数,必选
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:
3. 注意:如果没有返回每一行长什么样子,每行就会显示?;看见?,就知道没有实现每一行长什么样子的方法,要设置每一行显示什么样子就要设置代理,并实现对应方法。
五、代理
1. 设置代理的两种方式:a.拖线 b.代码
2. 代理协议方法
// 设置第component列第row行显示的内容
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component;
// 设置第component列第row行显示的视图
- (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view;
// 当选中第component列第row行的时候,就调用该方法
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component;
// 设置第component列的宽度
- (CGFloat)pickerView:(UIPickerView *)pickerView widthForComponent:(NSInteger)component;
// 设置第component列的行高
- (CGFloat)pickerView:(UIPickerView *)pickerView rowHeightForComponent:(NSInteger)component;
六、注意
1. PickerView的高度iOS9之前不能改,默认216,即使修改了也还是216;在iOS9上设置高度为0,PickerView会不显示
2. PickerView里面每行的高度可以改
3. 系统自带的控件,数据源和代理属性不需要IBOutlet,也能拖线。自己定义的属性,想要拖线,必须写IBOutlet。
七、使用方法
(一). 独立的,没有任何关系 => 点餐系统。
1. 在Storyboard中搭建好界面,并通过拖线设置数据源和代理
2. 设置加载数据的属性,通过重写Getter方法实现懒加载
3. 实现数据源协议方法和代理协议方法
4. 具体代码
#import "ViewController.h"
@interface ViewController () <UIPickerViewDataSource,UIPickerViewDelegate>
@property (weak, nonatomic) IBOutlet UIPickerView *pickerView;
@property (nonatomic,strong) NSMutableArray *foodsData;
@property (weak, nonatomic) IBOutlet UILabel *shuiguoLabel;
@property (weak, nonatomic) IBOutlet UILabel *zhushiLabel;
@property (weak, nonatomic) IBOutlet UILabel *yinliaoLabel;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//让pickerView一开始默认选中的数据显示在文本框中
for (int i = 0; i < self.foodsData.count; i++) {
[self pickerView:nil didSelectRow:0 inComponent:i];
}
}
#pragma mark - 懒加载数据
- (NSMutableArray *)foodsData {
if (_foodsData == nil) {
NSString *path = [[NSBundle mainBundle] pathForResource:@"foods.plist" ofType:nil];
_foodsData = [NSMutableArray arrayWithContentsOfFile:path];
}
return _foodsData;
}
#pragma mark - <UIPickerViewDataSource>
// 设置PickerView有多列,必选
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
return self.foodsData.count;
}
// 设置PickerView每一列有多少行,必选
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
return [self.foodsData[component] count];
}
#pragma mark - <UIPickerViewDelegate>
//告诉PickerView每一行显示什么数据
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component {
return self.foodsData[component][row];
}
//告诉PickerView每一行显示什么视图;当也实现了pickerView:titleForRow:forComponent:;优先级更高的是pickerView:viewForRow:forComponent:reusingView:方法
//- (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view {
// UIView *viewSub = [[UIView alloc] init];
// viewSub.backgroundColor = [UIColor redColor];
// return viewSub;
//}
//设置列宽
- (CGFloat)pickerView:(UIPickerView *)pickerView widthForComponent:(NSInteger)component {
return 110;
}
//设置行高
- (CGFloat)pickerView:(UIPickerView *)pickerView rowHeightForComponent:(NSInteger)component {
return 40;
}
//滚动时调用此函数
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {
NSLog(@"%zd %zd",row,component);
if (component == 0) {
self.shuiguoLabel.text = self.foodsData[component][row];
} else if (component == 1) {
self.zhushiLabel.text = self.foodsData[component][row];
} else {
self.yinliaoLabel.text = self.foodsData[component][row];
}
}
#pragma mark - 随机生成
- (IBAction)shuiji {
for (int i = 0; i < self.foodsData.count; i++) {
//取出每一列的行总数
NSInteger count = [self.foodsData[i] count];
//取0~行总数之间的随机数
NSInteger arcCount = arc4random_uniform((u_int32_t)count);
//给pickerView设置选中
[self.pickerView selectRow:arcCount inComponent:i animated:YES];
//手动赋值
[self pickerView:nil didSelectRow:arcCount inComponent:i];
}
}
@end
(二). 相关联的,下一列和第一列有联系 => 省会城市选择
1. 解决二级联动的问题,两列同时滚动,会报角标越界错误
(1). 原因:
‘返回每一行的样子’的代理方法会经常调用,只要有新的一行出现就会调用。而‘返回每一行的样子’的代理方法,每次都会获取最新选中的省,而第0列展示的是之前选中的省,如果最新选中的省的城市总数小于之前选中的省的城市。就会报角标错误。
(2). 例如:
最新选中的城市只有有4个,但是之前选中的省会城市有10 行,当第1列滚到5就会报角标越界错误。
(3). 解决方式:
这里不能获取最新的选中省会,需要记录之前选中的, 且只需要记录一次,在选中一行的代理方法里记录。
(4). 注意:
在刷新城市之前记住省会角标,应该刷新的城市,是当前选中的省会的城市。
2.具体代码
#pragma mark - <UIPickerViewDataSource>
//设置PickerView的列数
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
return 2;
}
//设置PickerView每一列有多少行
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
if (component == 0) {
return self.provincesData.count;
} else {
//获取当前选中的省
SSProvinces *Provinces = self.provincesData[self.proIndex];
return Provinces.cities.count;
}
}
#pragma mark - <UIPickerViewDelegate>
//初始化省列表和城市列表
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component {
if (component == 0) { //初始化省列表
return [self.provincesData[row] name];
} else {
//获取当前选中的省
SSProvinces *Provinces = self.provincesData[self.proIndex];
return Provinces.cities[row];
}
}
//选择城市,给城市文本框赋值
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {
if (component == 0) { //滚动省,刷新城市列表
//记录选择当前省
self.proIndex = [pickerView selectedRowInComponent:0];
//刷新城市列表
[pickerView reloadComponent:1];
}
//获取选中省
SSProvinces *Provinces = self.provincesData[self.proIndex];
NSString *proName = Provinces.name;
//获取选中城市
NSInteger cityIndex = [pickerView selectedRowInComponent:1];
NSString *cityName = Provinces.cities[cityIndex];
//给城市文本框赋值
self.cityTextField.text = [NSString stringWithFormat:@"%@省 %@市",proName,cityName];
}
(三). 图文并帽 => 国旗选择
1. 在Storyboard中搭建好界面,并通过拖线设置数据源和代理
2. 设置加载数据的属性,通过重写Getter方法实现懒加载
3. 实现数据源协议方法和代理协议方法,在实现代理方法‘返回每一行的样子’的方法,选用- (UIView )pickerView:(UIPickerView )pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view;
4. 定义模型
---------- SSFlags.h
#import <Foundation/Foundation.h>
@interface SSFlags : NSObject
@property (nonatomic,strong) NSString *name;
@property (nonatomic,strong) NSString *icon;
+ (instancetype)flagsWithDic:(NSDictionary *)dic;
@end
---------- SSFlags.m
#import "SSFlags.h"
@implementation SSFlags
+ (instancetype)flagsWithDic:(NSDictionary *)dic {
SSFlags *flags = [[self alloc] init];
[flags setValuesForKeysWithDictionary:dic];
return flags;
}
@end
4. 通过代码或xib的方式封装view
---------- SSFlagsView.h
#import <UIKit/UIKit.h>
@class SSFlags;
@interface SSFlagsView : UIView
@property (weak, nonatomic) IBOutlet UILabel *guoqiLabel;
@property (weak, nonatomic) IBOutlet UIImageView *guoqiImage;
@property (strong,nonatomic) SSFlags *flags;
@end
---------- SSFlagsView.m
#import "SSFlagsView.h"
#include "SSflags.h"
@implementation SSFlagsView
- (void)setFlags:(SSFlags *)flags {
_flags = flags;
self.guoqiLabel.text = flags.name;
self.guoqiImage.image = [UIImage imageNamed:flags.icon];
}
@end
5. 具体代码
#import "ViewController.h"
#include "SSFlags.h"
#import "SSFlagsView.h"
@interface ViewController () <UIPickerViewDataSource,UIPickerViewDelegate>
@property (nonatomic,strong) NSMutableArray *flagData;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
#pragma mark - 懒加载数据
- (NSMutableArray *)flagData {
if (_flagData == nil) {
NSString *path = [[NSBundle mainBundle] pathForResource:@"flags.plist" ofType:nil];
NSArray *arr = [NSArray arrayWithContentsOfFile:path];
_flagData = [NSMutableArray array];
for (NSDictionary *dic in arr) {
SSFlags *flags = [SSFlags flagsWithDic:dic];
[_flagData addObject:flags];
}
}
return _flagData;
}
#pragma mark - <UIPickerViewDataSource>
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
return 1;
}
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
return self.flagData.count;
}
#pragma mark - <UIPickerViewDelegate>
- (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view {
SSFlagsView *flagsView = [[[NSBundle mainBundle] loadNibNamed:@"SSFlagsView" owner:nil options:nil] lastObject];
flagsView.flags = self.flagData[row];
return flagsView;
}
- (CGFloat)pickerView:(UIPickerView *)pickerView rowHeightForComponent:(NSInteger)component {
return 80.0;
}
@end