UIPickerView

一、简介

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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值