當一撮樣式一樣的視圖在工程中被多次使用的時候,為了方便使用,我們會想把他們抽成一個單獨的類,進行視圖的自定義.
比如我們要做一個這樣的東西:
這一塊由兩個東西組成:一個imageView和一個label。首先我們新建一個繼承自UIView的類MyView.
在MyView的.m文件里,你可以根據自己的意愿將兩個子控件設置成MyView的屬性或者成員變量,這里我們設置為屬性。
@property (nonatomic, strong) UIImageView *imageView;
@property (nonatomic, strong) UILabel *label;
那接下來,就是要向自定義的view里面添加控件咯。
通常的思路是重寫UIView的構造方法。那么這里要說第一個注意了:
1.要重寫UIView的initWithFrame:方法而不是init方法
為什么呢?因為當外部調用init的方法的時候,其內部也會默默地調用initWithFrame:方法,你不能保證別的同事在調用你的類的時候不會直接調用initWithFrame:方法,這時如果你僅重寫了init方法,那么兩個子控件便無從創建.
于是我們寫成這樣:
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
/* 添加子控件的代碼*/
}
return self;
}
接下開始添加子控件,不知道還會不會有小伙伴是這樣寫的:
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
self.imageView = [[UIImageView alloc]init];
self.imageView.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.width);
[self addSubview:self.imageView];
}
return self;
}
這樣有什么問題嗎?如果全部寫好運行起來,能看到imageView嗎?答案是不確定的.問題出現在給imageView的frame賦值那里.
imageView的寬和高直接用self.frame.size.width,但這個時候self.frame可能是沒有值的.
上面我們說過,如果外部調用了MyView的init方法,也會執行到這里,這時候frame還沒有賦值.
所以第二個注意:
2.不要在構造方法里面直接取自身(self,或者說本視圖)的寬高,這時候取到的寬高是不準的.
我想初學自定義tableViewCell的小伙伴都遇到過類似這樣的問題:
重寫cell的初始化方法向cell內添加子控件時
(假設cell的高度設為100,想要添加一個label在cell的底部),
于是這樣寫:
label.frame = CGRectMake(0,self.frame.size.height - 20, 100, 20),
運行出來卻發現添加的label并不在我們期望的位置(底部),
而是在cell比較偏上的位置(實際y的值是44-20而不是100-20).
然后在debug的時候發現:雖然cell的高度已經設定成為100,但在初始化方法里面取到的cell的高度仍然是默認的44.
這其實也是剛才說的原因導致的:我們不能在控件的構造方法里面取其frame或者bounds,這時候取值是不準確的.
所以在重新構造方法的時候,我們只需要把控件放進去,暫時先不用考慮他們在什么位置:
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
self.imageView = [[UIImageView alloc]init];
[self addSubview:self.imageView];
self.label = [[UILabel alloc]init];
self.label.textAlignment = NSTextAlignmentCenter;
[self addSubview:self.label];
}
return self;
}
那么在什么時候設置子控件的frame呢?
第三個注意:
3.在layoutSubViews方法里面布局子控件
如下:
- (void)layoutSubviews {
// 一定要調用super的方法
[super layoutSubviews];
// 確定子控件的frame(這里得到的self的frame/bounds才是準確的)
CGFloat width = self.bounds.size.width;
CGFloat height = self.bounds.size.height;
self.imageView.frame = CGRectMake(0, 0, width, width);
self.label.frame = CGRectMake(0, width, width, height - width);
}
這里要注意的就是需要在布局之前一定要先調用父類的layoutSubviews方法.
由于在這個方法里可以獲取MyView準確的寬和高,我們直接取它的寬高來設置imageView和label的寬高就可以
當然,子控件的創建不一定要寫在MyView的構造方法里面,既然聲明成為屬性,使用懶加載(重寫屬性的get方法)也是一個不錯的選擇.