iOS进阶iOS开发基础控件的封装

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

2017-07-12  本文已影响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中打开”,就可以点赞咯~


上一篇 下一篇

猜你喜欢

热点阅读