自定义view创建
1.纯代码的方式创建自定义View
自定义view的基本步骤
1.重写 - (instancetype)initWithFrame方法,在此方法中创建并添加子控件。
2.提供一个便利的构造方法,通常为 类方法,快速创建一个实例对象
3.重写 - (void)layoutSubviews方法,在此方法中设置子控件的frame,
一定要调用[super layoutSubviews]
4.设置模型属性,在set方法中,给对应的子控件赋值。
具体实现代码
XYBookView.h 头文件
#import
@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];
}
使用关联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中打开”,就可以点赞咯~