zoukankan      html  css  js  c++  java
  • UIPickView 和 UIDatePicker

    UIPickView 和 UIDatePicker

    • 1.UIPickView什么时候用?
      • 通常在注册模块,当用户需要选择一些东西的时候,比如说城市,往往 弹出一个PickerView给他们选择。
    • 2.UIPickView常见用法
      • 独立的,没有任何关系 =>菜单系统。
      • 相关联的,下一列和第一列有联系=>省会城市选择
      • 图文并茂,=>国旗选择。
    • 3.UIDatePicker什么时候用?
      • 当用户选择日期的时候,一般弹出一个UIDatePicker给用户选择。

    01-UIPickView的基本使用

    • UIPickView和TableView一样,想要展示数据也要设置数据源和代理
    • 设置数据源
      • self.pickView.dataSource = self;
    • 设置代理
      • self.pickView.delegate = self;
    • 遵守数据源,代理协议:

      @interface ViewController () <UIPickerViewDataSource,UIPickerViewDelegate>
      @property (weak, nonatomic) IBOutlet UIPickerView *pickView; 
      @end
    • 实现数据源代理方法:

      总共有多少列
      - (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView{
      return 3; }
      
      第component列有多少行.
      - (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component{
      return 4; }
      
      返回每一列的宽度
      - (CGFloat)pickerView:(UIPickerView *)pickerView widthForComponent: (NSInteger)component{
      }
      
      返回第几列的高度
      - (CGFloat)pickerView:(UIPickerView *)pickerView rowHeightForComponent:(NSInteger)component{
      return 50; }
      
      返回每一列的标题
      - (nullable NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component{
      return @"gaowei"; }
      
      返回每一列的视图UIView
      - (UIView *)pickerView:(UIPickerView *)pickerView viewForRow: (NSInteger)row forComponent:(NSInteger)component reusingView: (nullable UIView *)view{
        UIButton *btn = [UIButton buttonWithType:UIButtonTypeContactAdd];
      return btn; } 

    02-UIPickView简单Demo

    • import "ViewController.h"
    • @interface ViewController ()<UIPickerViewDataSource,UIPickerViewDelegate> 
      //展示数据的pickView
      @property (weak, nonatomic) IBOutlet UIPickerView *pickView;
      
      //数组当中有3个数组,每个数组代表一列.每一列数组的个数代表这列有多少行.
      @property(nonatomic,strong) NSArray *foodArray;
      
      //显示当前选中的食物
      @property (weak, nonatomic) IBOutlet UILabel *foodLabel;
      @end    
      
      @implementation ViewController
      //懒加载数据
      -(NSArray *)foodArray{
      if (_foodArray == nil) { //获取Plist文件的路径
      NSString *filePath = [[NSBundle mainBundle] pathForResource:@"foods.plist" ofType:nil];
      //从Plist文件当中加载数组.
      _foodArray = [NSArray arrayWithContentsOfFile:filePath];
      }
      return _foodArray;
      }
      
      - (void)viewDidLoad {
      [super viewDidLoad];
      //设置数据源和代理
      self.pickView.dataSource = self; 
      self.pickView.delegate = self;
      }
      
      //总共有多少列
      - (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView{ //数组当中有几个元素, 就展示多少列.每个元素代表一列,
      return self.foodArray.count; }
      //第component列有多少行.
      - (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent: (NSInteger)component{
      //取出当前所在的列.每一列都是一个数组.
      NSArray *array = self.foodArray[component]; 返回每一组当中, 每个数组的个数, 也就是这组有多少行.
       return array.count;
      }
      
      返回每一行的标题
      - (nullable NSString *)pickerView:(UIPickerView *)pickerView titleForRow:
      (NSInteger)row forComponent:(NSInteger)component{
      //取出当前所在的列.每一列都是一个数组.
      NSArray *componentArray = self.foodArray[component];
      //返回数组当中每一个元素
      return componentArray[row]; 
      }
      
      //点击了哪列的哪一行  
      - (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component{
      NSString *food = self.foodArray[component][row]; self.foodLabel.text = food;
      }

    03-拦截用户输入

    - 通过设置代理的方式来去监听文件框的改变
    
        //是否允许开始编辑
        - (BOOL)textFieldShouldBeginEditing:(UITextField *)textField{ return YES;
        }
    
        //开始编辑时调 ,(弹出键盘)became first responder
        - (void)textFieldDidBeginEditing:(UITextField *)textField{
        NSLog(@"弹出键盘"); 
        }
    
        //是否允许结束编辑.
        - (BOOL)textFieldShouldEndEditing:(UITextField *)textField{
           return YES; 
    }
    
        //结束编辑时调用.
        - (void)textFieldDidEndEditing:(UITextField *)textField{
        NSLog(@"结束编辑时调用."); 
    }
    
        //是否允许文字改变.
        //拦截用户输入.
    - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string{
        return NO; 
    }

    04- 定义国旗键盘

    • 运行例效果:

      • 点击国家时弹出的是一个国旗键盘.
      • 实现这种效果采取的方案是自定义一个TextField.修改它的弹出键盘为 个UIPickView.

      • 对应的模型:

      • #import <UIKit/UIKit.h>
        注意:这里继承的是UITextField
        @interface FlagField : UITextField
        @end
        
        //这个是自定义的pickView每一行的一个UIView. 
        #import "FlagView.h" 
        #import "FlagItem.h"//这个是每一行对应的模型.
        @interface  FlagField()<UIPickerViewDataSource,UIPickerViewDelegate> 
        //保存加载的过期数组.
        @property(nonatomic,strong) NSMutableArray *flagArray;
        @end
        
        @implementation FlagField
        //加载数据 
        //数据内容对应的模型图:
        
        //数组当中保存的都是字典.我们看到字典就会它转成模型.
        所以新建了一个 FlagItem模型,FlagItem每一个属性,都对应着字典当中的key值.
        //懒加载数据
        -(NSMutableArray *)flagArray{
        if (_flagArray == nil) {
        //加载plist 件路径
        NSString *filePath = [[NSBundle mainBundle] pathForResource:@"flags.plist" ofType:nil];
        //根据路径从plist文件当中加载数组
        NSArray*dictArray =[NSMutableArrayarrayWithContentsOfFile:filePath];
        //创建数组
        _flagArray = [NSMutableArray array]; 
        for (NSDictionary *dict in dictArray) {//遍历数组当中的每一个元素,数组当中保存的都是字典.
        //把字典转成FlagItem模型
        FlagItem *item = [FlagItem flagItemDict:dict];
        //把模型添加到数组当中
        [_flagArray addObject:item]; }
            }
        return _flagArray;
        }
        注意:这个地方做了两个初始化,目的是为了不论别人使用这个FlagField是从xib创建,还是从代码创建,都让它做初始化. 
        //从xib当中创建
        -(void)awakeFromNib{
        //初始化.
          [self setUp];
        }
        //从代码创建
        - (instancetype)initWithFrame:(CGRect)frame{
          if (self = [super initWithFrame:frame]) {
          //初始化.
            [self setUp];
            }
          return self;
        }
        
        //初始化.
        - (void)setUp{
        //创建UIPickerView
        UIPickerView *pick = [[UIPickerView alloc] init]; 
        //设置代理
        pick.delegate = self; 
        //设置数据源
        pick.dataSource = self;
        
        self.inputView = pick;
        }
        
        //数据源方法
        //总共有多少列.
        -(NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView{
        return 1; }
        
        //总共有多少行  
        -(NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component{ 看数组当中有多少个模型,就有多少行.
        return self.flagArray.count; }
        //设置每一行的高度
        -(CGFloat)pickerView:(UIPickerView *)pickerView rowHeightForComponent:(NSInteger)component{ 这个 度就是xib当中描述的View的 度
        return 90; 
        }
        返回每一行的控件.这里的控件是一个UIView.通过Xib描述的模型.
        -(UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(
        //快速创建一个FlagView
        FlagView *flagV = [FlagView flagView];
        //取出当前行对应的模型
        FlagItem *item = self.flagArray[row]; 把模型赋值给FlagView的item属性,就会调用FlagItem的set方法,在set方法当中给FlagItem当中的子控件进行赋值.
        flagV.flagItem = item; 返回FlagView视图. return flagV;
        }
      • 执行过程:
      • 05-KVC模型改进- 定义国旗键盘

    • 在给 FlagView成员属性赋值的时候,发现在这里还得要自己去写UIImage, 手动去创建UIImage ,一般在模型当中保存的就是控件最想要的东西.那个这个地方,我们最好在模型当中提供的是一张图片.

      -(void)setFlagItem:(FlagItem *)flagItem{ _flagItem = flagItem;
      //给属性赋值.
      //设置名称
      self.name.text = flagItem.name;
      //设置图片.
      self.imageV.image = [UIImage imageNamed:flagItem.icon];
      }
    • 所以在这里进行改进模型,让模型当中存放的就是用户最想要的东西. 改过之后会导致一个问题,在KVC给属性赋值的时候,字典当中给的是一个字符串.这样在转的过程当中就会造成类型不匹配,发生错误.

    • 这个时候我们就要看下KVC的底层实现原理,看过后,然后通过它的特性,去避免这个问题

    • 这个是最初的字典转模型的方法.

      +(instancetype)flagItemDict:(NSDictionary *)dict{
      //创建对象
      FlagItem *item = [[self alloc] init]; 
      //通过KVC给对象的属性赋值.
      [item setValuesForKeysWithDictionary:dict]; 
      //返回 个赋值好属性的对象.
      return item;
      }
      
      //通过KVC,调用对象的[item setValuesForKeysWithDictionary:dict] 
      setValuesForKeysWithDictionary:底层实现就是遍历字典当中的所有Key和Value值.给对应的key,value赋值
      [dictenumerateKeysAndObjectsUsingBlock:^(id _Nonnullkey,id _Nonnull obj, BOOL * _Nonnull stop) {
      //给对应的key,value赋值
      [item setValue:obj forKeyPath:key];
      }];
    • setValue:forKeyPath:的底层实现:

      1.它会调用这个属性的set方法. 
      2.如果没有set方法,它会去判断有没有跟key值同名的成员属性.如果有,就直接赋值,icon = obj. 
      3.如果没有,那么它还会去判断有没有跟key值名相同带有下划线的成员属性,如果有,就直接赋值,_icon = obj.
      4.如果都没有, 就直接报错.找不到对应的成员属性.
      利用KVC会调用属性的set方法. 当给icon属性赋值时,把传进来的字符串当作图片的名称,创建一个图片,再给图片进行赋值.
      这个的参数,类型是可以改的
      - (void)setIcon:(NSString *)icon{
          UIImage *image = [UIImage imageNamed:icon];
          _icon = image; 
          }
      
      那在View当中就可以直接赋值图片了. 
      -(void)setFlagItem:(FlagItem *)flagItem{
          _flagItem = flagItem; 给属性赋值.
        self.name.text = flagItem.name; //设置名称
        //self.imageV.image = [UIImage imageNamed:flagItem.icon]; 模型当中保存的应该是最想的东西,所以在模型当中保存的应该是图片.
        self.imageV.image = flagItem.icon;//设置图片.
      }

      06-自定义生日键盘

      运行示例效果:

      • 点击生日时弹出的是一个日期键盘. 实现这种效果采取的方案是自定义一个TextField.修改它的弹出键盘为一个 UIDatePicker.

    • #import <UIKit/UIKit.h> 注意:这 继承的是UITextField
      @interface BirthDayField : UITextField
      @end
      
      #import "BirthDayField.h"
      @implementation BirthDayField 
      注意:这个地方做了两个初始化,目的是为了不论别人使用这个FlagField是从xib创建,还是从代码创建,都让它做初始化.
      //从xib当中创建 
      -(void)awakeFromNib{
      //初始化.
      [self setUp];
      }
      
      //从代码创建
      - (instancetype)initWithFrame:(CGRect)frame{
          if (self = [super initWithFrame:frame]) {
          //初始化.
          [self setUp];
           }
          return self;
      }
      
      //初始化方法
      - (void)setUp{
      //创建UIDatePicker(日期键盘)
        UIDatePicker *pick = [[UIDatePicker alloc] init]; 
        pick.datePickerMode = UIDatePickerModeDate; ISO639语 编码(中国zh -zhongwen)
        NSLocale *local = [NSLocale localeWithLocaleIdentifier:@"zh"];
        pick.locale = local;
      //UIDatePicker没有代理方法
      //监听UIDatePicker的值改变.
        [pick addTarget:self action:@selector(dateChange:)forControlEvents:UIControlEventValueChanged];
      // 定义键盘, 让弹出的键盘是 个UIPickerView.( 定义的键盘是不需要设置尺寸的.)
        self.inputView = pick;
      }
      
      //当日期改变时调 
      - (void)dateChange:(UIDatePicker *)datePick{
      //把日期转成字符串.
          NSDateFormatter *fmt = [[NSDateFormatter alloc] init]; 
      //设置日期格式
          fmt.dateFormat = @"yyyy-MM-dd";
      //格式化日期.
          NSString *dateString = [fmt stringFromDate:datePick.date];
      //给日期文本框赋值.
          self.text = dateString;
      }
      @end

      07- 定义城市键盘

      #import <UIKit/UIKit.h> 
      注意:这里继承的是UITextField
      @interface CityTextField : UITextField
      @end
      
      #import "CityTextField.h"
      省模型
      #import "ProvincesItem.h"
      @interface CityTextField()<UIPickerViewDataSource,UIPickerViewDelegate>
      //省份数组.装的都是ProvincesItem模型. 
      @property(nonatomic,strong) NSMutableArray *provincesDataArray;
      //记录当前选中的是哪一个省份的角标.
      @property(nonatomic, assign) NSInteger curProIndex;
      @end
    • 加载数据

    • 数据内容对应的模型图:

    • 数组当中保存的都是字典.我们看到字典就会它转成模型.

    • 所以新建了一个 ProvincesItem模型,ProvincesItem每一个属性,都对应着字典当中的key值.

      懒加载省份数据
      -(NSMutableArray *)provincesDataArray{
      if (_provincesDataArray == nil) { 
      //创建数组
      _provincesDataArray = [NSMutableArray array]; 
      //获取plist文件的路径
      NSString*filePath= [[NSBundlemainBundle]pathForResource:@"provinces.plist" ofType:nil]; 
      //从plist 文件当中加载数据
      NSArray *tempArray = [NSArray arrayWithContentsOfFile:filePath]; 
      //遍历数组当中的每一个元素.每个元素都是一个字典,把字典转成模型
      for (NSDictionary *dict in tempArray) {
      //创建省份模型对象.快速的将字典转成模型
      ProvincesItem*item= [ProvincesItemprovincesItemWithDict:dict]; 把转好的对象保存到数组当中.
      [_provincesDataArray addObject:item]; }
          }
      return _provincesDataArray;
      }
      
      -(void)awakeFromNib{ 
      //初始化.
      [self setUp];
      }
      
      - (instancetype)initWithFrame:(CGRect)frame{
          if (self = [super initWithFrame:frame]) {
      //初始化.
      [self setUp];
          }
      return self;
      }
      //初始化方法
      - (void)setUp{
      //创建UIPickerView
      UIPickerView *pickV = [[UIPickerView alloc] init]; 
      //设置代理
      pickV.delegate = self; 
      //设置数据源.
      pickV.dataSource = self; 
      //设置弹出键盘是一个UIPickerView 
      self.inputView = pickV;
      }
      
      实现代理协议方法
      //总共有多少组
      - (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView{
      //总共有两组
      return 2; 
      }
      //每组有多少行 
      - (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component{
      //第一组展有多少行,由省份数组决定, 数组当中有多少个省份模型,第一组就有多少行. 
      if (component == 0) {
          return self.provincesDataArray.count;
          }else{
      //第二组展有多少行,由当前选中的省份决定,当前选中哪个省份.哪个省份下的城市数组决定. 所以要先获取当前先中的是哪个省.
      ProvincesItem *proItem = self.provincesDataArray[self.curProIndex];
      //返回当前省份下城市的个数
          return proItem.cities.count;
          }
      }
      
      返回每一行展示的标题
      - (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component{
      如果是第0列,就是省份,返回省份的名称 if (component == 0) {
      ProvincesItem *proItem = self.provincesDataArray[row];
      return proItem.name;
      }else{
      先取出选中的省份.
      ProvincesItem *proItem = self.provincesDataArray[self.curProIndex];
      //返回选中省份下,每个城市. 
      return proItem.cities[row];
          } 
      }
      
      选中某一列的某一行时会调用这个方法
      - (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)rowinComponent:(NSInteger)component{ 
      //当是第0列的时候.(省份列)
      if(component == 0){ 记录当前选中的省份的角标
      self.curProIndex = row; 
      //刷新列表.
      [pickerView reloadAllComponents]; 
      //让第1列的第0行成为选中状态.(让城市列每次刷新时,都选中第 0个)
      [pickerView selectRow:0 inComponent:1 animated:YES];
      }
      
      //把选中的省份,城市显示到文本框当中. 
      //获取选中的省.
      ProvincesItem *proItem = self.provincesDataArray[self.curProIndex]; 
      //获取省份的名称
      NSString *province = proItem.name;
      //获取第一列选中的行号.(城市列)
      NSInteger seleRow = [pickerView selectedRowInComponent:1];
      //获取当前选中省下选中的城市名称.
      NSString *city = proItem.cities[seleRow];
      //显示数据
      self.text = [NSString stringWithFormat:@"%@ %@",province,city];
      }

      08- 定义城市键盘(初始化文字处理)

    • 当光标点击文本框的时候,让文本框的文字为第一个自定义键盘选中的文字. 所以监听文本框架的开始编辑的代理 开始编辑时调用,(弹出键盘)became first responder

      - (void)textFieldDidBeginEditing:(UITextField *)textField{
      }
    • 在这个代理方法当中做事情.每一个文本框当中都提供一个初始化的方法.当在控制器当中调用这个初始化的方法.方法的目的就是让开始编辑选中第一列的第一行.

      - (void)initWithText;
      国旗键盘初始化方法实现: 
      - (void)initWithText{
      [self pickerView:nil didSelectRow:0 inComponent:0]; }
      //日期键盘初始化方法实现: 
      - (void)initWithText{
      [self dateChange:self.pick]; }
      //城市键盘初始化方法实现: 
      - (void)initWithText{
      [self pickerView:self.pick didSelectRow:0 inComponent:0];
      }

      把方法的参数改成国旗,或者日期,或者城市的类型,改的一个目的是让它能够找到 initWithText 法. 传进来的参数真实类型是什么类型,它就会去调用它真实类型的方法. 如果传进来的键盘类型为日期键盘,它就会调日期的初始化方法.如果真实类型为城市,它就会去调用城市的初始化方法.

      - (void)textFieldDidBeginEditing:( FlagField *)textField{ 会去找它真实类型的方法
      [textField initWithText];
      }

      还有一个方法就是在UITextFiled的分类中声明 initWithText 方法,那么在方法 - (void)textFieldDidBeginEditing:( UITextField *)textField{ [textField initWithText]; }

    • 补充点:类的加载过程
    • 1)在加载类的load方法时,系统会先加载控制器,然后是子类,再是分类,最后才是父类
    • 2)在加载类的initialize方法时,系统会先加载控制器,然后是父类,再是分类,最后是子类.
  • 相关阅读:
    我们的故事
    实验三 进程调度模拟程序
    Java环境配置XXX系统(标题党)
    .Net多线程和线程通信(标题党)
    关于数据库死锁,数据库脏数据和产生的原因,数据库事务(标题党)
    微服务架构(一):什么是微服务
    .NET Core 实践一:微服务架构的优点(转)
    .NET Core 实践二:事件通知和异步处理
    设计模式之单例模式
    数组式访问-ArrayAccess
  • 原文地址:https://www.cnblogs.com/zhoudaquan/p/5014078.html
Copyright © 2011-2022 走看看