本文重点
- 关于自定义View的初始化方法
- 关于addSubview
- 关于frame与bounds
关于自定义View的初始化方法
通常我们会创建私有方法createUI方法来创建当前自定义View所需要的子View。将createUI放在哪个方法中呢?
- init()?
- initWithFrame()?
通过验证说明:首先在CustomView的init方法中调用createUI方法。
- (instancetype)init {
if (self = [super init]) {
[self createUI];
}
return self;
}
- (void)createUI {
[self addSubview:self.testView];
}
- (UIView *)testView {
if (!_testView) {
_testView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
_testView.backgroundColor = [UIColor redColor];
}
return _testView;
}
外部用init的形式进行创建CustomView:
_ceshiView = [[CeShiView alloc] init];
_ceshiView.frame = CGRectMake(100, 100, 200, 200);
_ceshiView.backgroundColor = [UIColor lightGrayColor];
[self.view addSubview:_ceshiView];
CustomView与其子视图均可正常显示。
但有个问题是,如果外部以initWithFrame形式创建,无法调用createUI方法,因此子视图无法显示。
在init与initWithFrame方法中均调用createUI方法。计算createUI方法调用的次数。
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[self createUI];
}
return self;
}
- (instancetype)init {
if (self = [super init]) {
[self createUI];
}
return self;
}
- (void)createUI {
NSLog(@"SubViews Add");
[self addSubview:self.testView];
for (NSInteger i = 0; i < 100; i++) {
[self addSubview:self.testView];
}
NSLog(@"subviewsCount = 【%ld】",self.subviews.count);
for (UIView *view in self.subviews) {
NSLog(@"subView 【%@】",view);
}
}
- (UIView *)testView {
if (!_testView) {
_testView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
_testView.backgroundColor = [UIColor redColor];
}
return _testView;
}
@end
最后得知,createUI被调用了两次。
2022-01-12 17:06:43.112130+0800 WJ_KVO[8774:258969] SubViews Add
2022-01-12 17:06:43.113792+0800 WJ_KVO[8774:258969] SubViews Add
自定义View的init方法是否会默认调用initWithFrame方法呢?
1、动态查找到CustomView的init方法
2、调用[super init]方法
3、super init方法内部执行的的是[super initWithFrame:CGRectZero]
4、若super发现CustomView实现了initWithFrame方法
5、转而执行self(CustomView)的initWithFrame方法
6、最后在执行init的其余部分
这里也可以验证一个结论:OC中的super实际上是让某个类去调用父类的方法,而不是父类去调用某个方法,方法动态调用过程顺序
由下而上的(这也是为什么只在init方法中进行createUI不会执行多次的原因,因为父类的initWithFrame没做createUI操作)。
结论: createUI方法最好在initWithFrame中调用,外部使用init或initWithFrame均可以正常执行createUI方法。不要在自定义
View中同时重写init与initWithFrame并执行相同视图布局代码。会导致布局代码(createUI)执行多次。
关于addSubview
重复多次添加同一个View并不会产生多层级的情况。
addSubview的文档描述,View有且仅有一个父视图,如果新的父视图与原父视图不一样,会将View在原视图中移除,添加到新视图上。
22-01-12 17:22:35.726706+0800 WJ_KVO[8970:267845] subviewsCount = 【1】
2022-01-12 17:22:35.726799+0800 WJ_KVO[8970:267845] subView 【<UIView: 0x7f78a7f075f0; frame = (0 0; 100 100); layer = <CALayer: 0x6000013c9960>>】
如上面打印所示,CeshiView始终只有一个子视图(testView)
可以作出如下假设:
1、在旧父视图中移除子视图,再重新将子视图添加到父视图上
2、判断新旧父视图是否一致,若一致,不做任何操作。
结论:若父视图重复添加同一子视图,并不会产生多层级情况。因为此例中testView是以懒加载的形式创建,所以self每次添加的均为同一个View,但如果在createUI中以UIView *testView = [UIView alloc] initWithFrame的形式创建,那就会创建出多层级的View。
总结:自定义View的子视图最好以懒加载形式创建,可避免因其他书写不当导致的异常
关于Frame与bound
iOSUI控件中关于位置主要是两个非常重要的属性,frame与bound.
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7He9Zeod-1642056027779)(/Users/yichen/Library/Application Support/typora-user-images/image-20220112172848089.png)]
Frame:
这个主要是View相当于父视图所在的位置。
bound:
这个是View相当于相对于控件本身的位置。
center:
这个相对于父视图控件的中心位置。
我们可以通过 frame.origin/bounds.origin 与frame.size/bounds.size来进行返回控件左上角位置与大小