美文网首页iOS进阶iOS开发基础控件的封装
iOS 自定义view创建和使用问题积累

iOS 自定义view创建和使用问题积累

作者: 楚简约 | 来源:发表于2017-07-12 16:54 被阅读613次

    自定义view创建

    1.纯代码的方式创建自定义View

    自定义view的基本步骤

    1.重写 - (instancetype)initWithFrame方法,在此方法中创建并添加子控件。
    2.提供一个便利的构造方法,通常为 类方法,快速创建一个实例对象
    3.重写 - (void)layoutSubviews方法,在此方法中设置子控件的frame,
      一定要调用[super layoutSubviews]
    4.设置模型属性,在set方法中,给对应的子控件赋值。
    

    具体实现代码

    XYBookView.h 头文件
    
    #import <UIKit/UIKit.h>
    @class XYBook;
    @interface XYBookView : UIView
    // 只放一个数据属性用来赋值,内部布局,放到.m 中自己管,不暴露给外界
    @property (nonatomic, strong) XYBook *book;
    @end
    
    实现文件 .m文件
    #import "XYBookView.h"
    #include "XYBook.h"         //模型
    
    @interface XYBookView ()
    // 两个内部子控件在内部包装起来,不给外界看到
    @property (nonatomic, weak) UIImageView *icon;
    
    @property (nonatomic, weak) UILabel *label;
    
    @end
    
    @implementation XYBookView
    
    // 1.重写initWithFrame:方法,创建子控件并添加到自己上面
    - (instancetype)initWithFrame:(CGRect)frame {
        if (self = [super initWithFrame:frame]) {
    
            // 1. 创建书图标
            UIImageView *icon = [UIImageView new];
            self.icon = icon;
            [self addSubview:self.icon];
    
            // 2.书名
            UILabel *bookName = [UILabel new];
            bookName.textAlignment = NSTextAlignmentCenter;
            self.label = bookName;
            [self addSubview:self.label];
    
        }
        return self;
    }
    
    // 2.重写layoutSubviews,给自己内部子控件设置frame
    - (void)layoutSubviews {
        [super layoutSubviews];
        CGSize size = self.frame.size;
        self.icon.frame = CGRectMake(0, 0, size.width , size.height * 0.7);
        self.label.frame = CGRectMake(0, size.height * 0.7, size.width, size.height *(1 - 0.7));
    
    }
    
    // 3.调用模型的set方法,给书的子控件赋值,
    - (void)setBook:(XYBook *)book {
        _book = book;
        self.icon.image = [UIImage imageNamed:book.icon];
        self.label.text = book.name;
    }
    @end
    

    以上是纯代码实现的View的封装,写起来会麻烦点。

    使用纯代码封装创建自定义View的时候需要注意:

    1.一般来说我们的自定义类继承自UIView,首先在initWithFrame:方法中将需要的子控件加入view中。
    注意:这里只是加入到view中,并没有设置各个子控件的尺寸。并且是在initWithFrame方法中而不是init方法
    
    2.initWithFrame:中添加子控件。
    layoutSubviews中设置子控件frame。
    对外设置数据接口,重写setter方法给子控件设置显示数据。(model)
    在view controller里面使用init/initWithFrame:方法创建自定义类,并且给自定义类的frame赋值。
    对自定义类对外暴露的数据接口进行赋值即可。
    
    2.关联xib的方式创建自定义View

    xib关联自定义view的步骤:

    1. 创建一个自定义的view:Cocoa Touch Class
       创建UIView时候 Also create XIB file 的选项是不能被勾选的,与自定义cell不同
    2.创建一个同名的xib: User Interface -> View
    3.设置xib的File`s Owner的Custome Class属性为自定义的view: 
    4.然后在自定义的view里面重写你需要初始化的方法:
            NSArray *nibView =  [[NSBundle mainBundle] loadNibNamed:@"xib的名字"owner:self options:nil];
            UIView *backView = [nibView objectAtIndex:0];
            backView.frame = frame;
            [self addSubview:backView];
    
    方式一 :
    //重写initWithFrame构造方法
    - (instancetype)initWithFrame:(CGRect)frame
    {
        self = [super initWithFrame:frame];
        if (self) {
        //ARRewardView : 自定义的view名称
            NSArray *nibView =  [[NSBundle mainBundle] loadNibNamed:@"ARRewardView"owner:self options:nil];
            UIView *backView = [nibView objectAtIndex:0];
            backView.frame = frame;
            [self addSubview:backView];
        }
        return self;
    }
    
    方式二:
    //写一个创建自定义view的类方法
    + (instancetype)initCreatView {
        return [[[NSBundle mainBundle] loadNibNamed:@"ARRewardView"owner:self options:nil] objectAtIndex:0];
    }
    
    2.创建xib.png 3.关联xib.png

    使用关联xib自定义View编码是,需要注意:

    系统的调用流程: initWithCoder —> awakeFromNib —> layoutSubviews
    (1).加载XIB后,系统会自动调用 - (id)initWithCoder:(NSCoder *)aDecoder 
    方法来初始化控件,其中aDecoder是一个解析器,对XIB进行解析;
    不能在这个方法中给XIB里自定义view的子控件进行初始化,因为initWithCoder:方法是
    ‘处于正在初始化’,有些细节还没有初始化完毕,可能还没给子控件进行连线等,
    但是可以在initWithCoder:方法里对自定义View进行初始化,但不能设置View的Frame值,
    且一般XIB的初始化操作在awakeFromNib里进行
    
    (2).如过还需要用代码添加子控件,可以通过重写initWithCoder:方法,
    在方法里面用代码添加子控件和初始化,添加的子控件的Frame值也要在layoutSubviews方法里设置。
    
    (3).当控件从XIB中创建完毕后会调用awakeFromNib方法,XIB的所有的初始化操作应该在这个方法里进行,
    但不能在这个方法中对子控件设置Frame的值
    
    (4).如果需要重新设置子控件的Frame值,应该在layoutSubviews方法里进行设置,
    因为父控件的Frame只要改变就就会调用该方法
    
    (5).用XIB封装自定义的View,控件从XIB中创建的过程不会调用init方法和initWithFrame:方法
    (使用方式二方法创建)
    
    (6).XIB里自定义View必须设置成自动布局,即把View的 ‘Show the File inspector’ 里面的 ‘Use Auto Layout’ 前面的钩去掉;这样设置后才可以在layoutSubviews里重新设置自定义View的子控件Button的Frame值
    
    小结

    一个控件有两种创建方式

    通过代码创建
    初始化一定会调用 -(instancetype)initWithFrame:方法
    
    通过Xib\StoryBoard创建
    如果使用xib/storyboard方式创建控件,那么在创建时一定会调用initWithCoder:方法。
    初始化完成之后,回调用awakeFromNib方法
    
    通过两种加载方式,可以发现:有时候我们希望在控件初始化时做一些初始化的操作,
    如添加子控件,设置属性等,这时候需要根据控件的加载方式来选择
    -(instancetype)initWithFrame:,
    -(instancetype)initWithCoder:,
    awakeFromNib
    三个方法中的哪个方法进行初始化。
    

    常见问题

    ①使用xib关联自定义view中, 方式一的创建方法可能出现修改不了frame的问题

    HotProductView * proView = [[HotProductView alloc]initWithFrame:CGRectMake(x, y, w, h)];
    

    这样创建自定义view设置frame时发现设置不起作用或者不对, 解决办法是 在-(void)drawRect:(CGRect)rect里面重新设置frame

    HotProductView.m
    @interface HotProductView ()
    {
        CGRect myframe;
    }
    @end
    @implementation HotProductView
    -(id)initWithFrame:(CGRect)frame
    {
        self = [super initWithFrame:frame];
        if (self) {
            NSArray *nibs=[[NSBundle mainBundle]loadNibNamed:@"HotProductView" owner:nil options:nil];
            self=[nibs objectAtIndex:0];
            myframe = frame;
        }
        return self;
    }
    -(void)drawRect:(CGRect)rect {
        self.frame=myframe;//关键点在这里
    }
    @end
    

    ②使用xib关联自定义view中, 方式二的创建方法
    在initWithCoder:里面访问属性,比如self.button,会发现它是nil的,因为此时自定义控件正在初始化,self.button可能还未赋值(self.button是一个IBOutlet,IBOutlet本质上就相当于Xcode找到这个对应的属性,然后UIButton button = … , [self.view addSubview: button]这种操作,而这一切的操作都是相当于在CYLView view = [[CYLView alloc] initWithCoder: nil]方法之后执行的。上面的代码就相当于用代码的方式实现Xcode在storyboard中加载CYLView),所以如果在这个方法中进行初始化操作是可能会失败的。

    所以建议在awakeFromNib方法中进行初始化的额外操作。因为awakeFromNib是在初始化完成后调用,所以在这个方法里面访问属性(IBOutlet)就可以保证不为nil。

    ---------- 数据模型的.h文件
    @interface SSData : NSObject
    @property (nonatomic,strong) NSString *buttonStr;
    @property (nonatomic,strong) NSString *lableStr;
    @end
     
    ---------- 数据模型的.m文件
    #import "SSData.h"
    @implementation SSData
    @end
    
    ---------- 新建自定义控件类的.h文件
    #import "SSData.h"
    @interface ANewView : UIView
    @property (nonatomic,strong) SSData *data;
    + (instancetype)aNewView;//方式二创建类的方法
    @end
     
    ---------- 新建自定义控件类的.m文件
    #import "ANewView.h"
    @interface ANewView ()
    @property (weak, nonatomic) IBOutlet UIButton *button;
    @property (nonatomic,weak) IBOutlet UILabel *lable;
    @property (nonatomic,strong) UIImageView *imageView;
    @end
    
    @implementation ANewView
    + (instancetype)aNewView {
      //UINib *nib = [UINib nibWithNibName:NSStringFromClass(self) bundle:nil];
      //ANewView *view = [[nib instantiateWithOwner:nil options:nil]lastObject];
     
      return  [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass(self) owner:nil options:nil] lastObject];;
    }
     
    //重写initWithCoder:用代码添加子控件
    - (id)initWithCoder:(NSCoder *)aDecoder {
      if (self = [super initWithCoder:aDecoder]) {
        //可以用代码创建子控件并对其初始化
        self.imageView = [[UIImageView alloc] init];
        [self addSubview:self.imageView];
        self.imageView.backgroundColor = [UIColor yellowColor];
      }
      return self;
    }
     
    //当XIB完全初始化完毕后,会调用这个方法,可以在这个方法中对XIB的子控件进行初始化
    -(void)awakeFromNib {
      //一定要调用父类的awakeFromNib方法
      [super awakeFromNib];
      self.button.backgroundColor = [UIColor yellowColor];
      self.lable.backgroundColor = [UIColor orangeColor];
     
    }
     
    //给子控件重新布局
    - (void)layoutSubviews {
      //一定要调用父类的layoutSubviews方法
      [super layoutSubviews];
      //设置子控件的Frame
      CGFloat superW = self.frame.size.width;
      CGFloat superH = self.frame.size.height;
      //XIB里自定义View必须设置成自动布局,不然Button的Frame不能成功赋值
      self.button.frame = CGRectMake(0, 0, superW, superH / 3 - 5);
     
      CGFloat lableY = self.button.frame.origin.y + self.button.frame.size.height + 5;
      self.lable.frame = CGRectMake(0, lableY, superW, superH / 3 - 5);
     
      CGFloat imageY = self.lable.frame.origin.y + self.lable.frame.size.height + 5;
      self.imageView.frame = CGRectMake(0,imageY, superW, superH / 3 - 5);
    }
     
    //重写set方法给子控件设置数据
    - (void)setData:(SSData *)data {
      //必须先给成员变量赋值,不赋值,以后调用get方法取值就取不到值
      _data = data;
     
      //设置成员变量的数据
      [self.button setTitle:data.buttonStr forState:UIControlStateNormal];
       self.lable.text = data.lableStr;
    }
    @end
    
    ---------- XIB封装的自定义View的调用
    #import "ViewController.h"
    #import "ANewView.h"
     
    @interface ViewController ()
    @end
     
    @implementation ViewController
    - (void)viewDidLoad {
      [super viewDidLoad];
      //数据模型
      SSData *dataView = [[SSData alloc] init];
      dataView.buttonStr = @"Hello";
      dataView.lableStr = @"World";
     
      //创建View
      ANewView *newView = [ANewView aNewView];
      newView.frame = CGRectMake(100,100, 200, 100);
      newView.data = dataView;
      [self.view addSubview:newView];
     
      //重新给View对象赋值
      dataView.buttonStr = @"天天";
      dataView.lableStr = @"编程";
      newView.frame = CGRectMake(80,200, 250, 200);
      newView.data = dataView;
    }
    @end
    

    后续如果有问题会继续补充.....

    最后介绍一下 drawRect:和layoutSubview的区别
    layoutSubviews方便数据计算,drawRect方便视图重绘。


    我是楚简约,感谢您的阅读,

    喜欢就点个赞呗,“❤喜欢”,

    鼓励又不花钱,你在看,我就继续写~

    非简书用户,可以点右上角的三个“...”,然后"在Safari中打开”,就可以点赞咯~


    相关文章

      网友评论

      本文标题:iOS 自定义view创建和使用问题积累

      本文链接:https://www.haomeiwen.com/subject/oxlbhxtx.html