IOS開發(fā)自定義view方法規(guī)范示例
前言
對于接觸業(yè)務(wù)開發(fā)的童鞋,自定義View的開發(fā)是進(jìn)行最頻繁的工作了。但發(fā)現(xiàn)一些童鞋還是沒有以一個好的規(guī)范甚至以一種錯誤的方式來搭建UI控件。由此,本文將以以下目錄來進(jìn)行講敘,詳細(xì)描述關(guān)于自定義View的一些書寫注意事項(xiàng)。
- 關(guān)于自定義View的初始化方法
- 關(guān)于addSubview
- 關(guān)于layoutSubviews
- 關(guān)于frame與bounds
一、關(guān)于自定義View的初始化方法
通常我們會創(chuàng)建私有方法createUI方法來創(chuàng)建當(dāng)前自定義View所需要的子View。那上述所說的createUI應(yīng)該放在自定義View的哪個方法中呢?
1、init?
2、initWithFrame?
3、還是為了考慮外部創(chuàng)建自定義View的方式不同,在init與initWithFrame方法中均調(diào)用createUI方法?
我們來一一驗(yàn)證,首先在CustomView的init方法中調(diào)用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形式創(chuàng)建CustomView
CustomView *customView = [[CustomView alloc] init]; customView.frame = CGRectMake(100, 100, 200, 200); customView.backgroundColor = [UIColor lightGrayColor]; [self.view addSubview:customView];
可驗(yàn)證,CustomView與其子視圖均可正常顯示。但有個問題是,如果外部以initWithFrame形式創(chuàng)建,無法調(diào)用createUI方法,因此子視圖無法顯示。
第二種初始化形式,單獨(dú)在initWithFrame方法中調(diào)用createUI方法
- (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { [self createUI]; } return self; }
可驗(yàn)證結(jié)果是,無論外部以init或者initWithFrame方法初始化CustomView,均可以正常顯示CustomView與其子視圖。
最后我們做個實(shí)驗(yàn),在init與initWithFrame方法中均調(diào)用createUI方法。調(diào)試createUI方法調(diào)用次數(shù)。
- (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]; } - (UIView *)testView { if (!_testView) { _testView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)]; _testView.backgroundColor = [UIColor redColor]; } return _testView; } @end
外部創(chuàng)建CustomView仍使用init形式 通過打印結(jié)果或斷點(diǎn)可驗(yàn)證createUI方法被執(zhí)行了兩次!
2019-06-26 17:17:51.961744+0800 TestAddSubview[72346:1989647] SubViews Add
2019-06-26 17:17:51.961917+0800 TestAddSubview[72346:1989647] SubViews Add
其實(shí),上述三種假設(shè)均和一個問題相關(guān),即自定義View的init方法是否會默認(rèn)調(diào)用initWithFrame方法。
答案是肯定的,通過上述的代碼調(diào)試流程,我們可以得到如下結(jié)論,關(guān)于代碼的調(diào)用過程(以外部初始化init為例):
1、動態(tài)查找到CustomView的init方法
2、調(diào)用[super init]方法
3、super init方法內(nèi)部執(zhí)行的的是[super initWithFrame:CGRectZero]
4、若super發(fā)現(xiàn)CustomView實(shí)現(xiàn)了initWithFrame方法
5、轉(zhuǎn)而執(zhí)行self(CustomView)的initWithFrame方法
6、最后在執(zhí)行init的其余部分
這里也可以驗(yàn)證一個結(jié)論:OC中的super實(shí)際上是讓某個類去調(diào)用父類的方法,而不是父類去調(diào)用某個方法,方法動態(tài)調(diào)用過程順序是由下而上的(這也是為什么只在init方法中進(jìn)行createUI不會執(zhí)行多次的原因,因?yàn)楦割惖膇nitWithFrame沒做createUI操作)。
結(jié)論: createUI方法最好在initWithFrame中調(diào)用,外部使用init或initWithFrame均可以正常執(zhí)行createUI方法。不要在自定義View中同時重寫init與initWithFrame并執(zhí)行相同視圖布局代碼。會導(dǎo)致布局代碼(createUI)執(zhí)行多次。
二、關(guān)于addSubview
我們接著問題一自定義View的初始化方法來說,如果同時在init與initWithFrame中同時調(diào)用了createUI方法,會有什么影響呢?
顯而易見的是createUI方法執(zhí)行了多次,也就是說重復(fù)多次添加了self.testView。那是否會重復(fù)添加多個View層呢?
并不會,重復(fù)多次添加同一個View并不會產(chǎn)生多層級的情況。 我們看下addSubview的文檔描述
This method establishes a strong reference to view and sets its next responder to the receiver, which is its new superview. Views can have only one superview. If view already has a superview and that view is not the receiver, this method removes the previous superview before making the receiver its new superview.
大概闡述的意思是,View有且僅有一個父視圖,如果新的父視圖與原父視圖不一樣,會將View在原視圖中移除,添加到新視圖上。
因此同一父視圖重復(fù)添加同一個View并不會產(chǎn)生多層級。 可以簡單通過代碼驗(yàn)證,我們在createUI中循環(huán)添加self.testView,最終打印當(dāng)前視圖的子視圖個數(shù)
- (void)createUI { 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); } }
運(yùn)行可見,視圖的子視圖個數(shù)始終為1
2019-06-28 16:02:50.420144+0800 TestAddSubview[78991:832644] subviewsCount = 【1】
2019-06-28 16:02:50.422151+0800 TestAddSubview[78991:832644] subView 【<UIView: 0x7f80a9c09590; frame = (0 0; 100 100); layer = <CALayer: 0x600003ff0a40>>】
根據(jù)打印結(jié)果可驗(yàn)證,CustomView始終只存在一個子視圖(testView)。
新舊父視圖一致,我們可以假設(shè)蘋果做了如下處理:
1、在舊父視圖中移除子視圖,再重新將子視圖添加到父視圖上
2、判斷新舊父視圖是否一致,若一致,不做任何操作。
因?yàn)闊o法看到addSubview的源碼,猜測可能會有這兩種情況,個人更偏向第二種處理。(可重寫子視圖layoutSubviews方法,因?yàn)閍ddSubviews會調(diào)用layoutSubviews方法,我們可以調(diào)試layoutSubviews的調(diào)用次數(shù),測試后可驗(yàn)證addSubviews做了上述二的處理)
結(jié)論:若父視圖重復(fù)添加同一子視圖,并不會產(chǎn)生多層級情況。因?yàn)榇死衪estView是以懶加載的形式創(chuàng)建,所以self每次添加的均為同一個View,但如果在createUI中以UIView *testView = [UIView alloc] initWithFrame的形式創(chuàng)建,那就會創(chuàng)建出多層級的View。
總結(jié):自定義View的子視圖最好以懶加載形式創(chuàng)建,可避免因其他書寫不當(dāng)導(dǎo)致的異常
三、關(guān)于layoutSubviews
關(guān)于這一點(diǎn),主要想聊一聊layoutSubviews的調(diào)用時機(jī)
1、setNeedsLayout\ layoutIfNeeded
2、addSubview
3、View的大小發(fā)生變化,未變不調(diào)用
4、UIScrollView滑動
5、旋轉(zhuǎn)Screen會觸發(fā)父UIView上的layoutSubviews事件
因此對于layoutSubviews的使用我們需要注意以下幾點(diǎn):
1、自定義視圖的init方法并不會調(diào)用layoutSubviews
2、蘋果聲明不要直接調(diào)用layoutSubviews方法,如果需要更新,應(yīng)該調(diào)用setNeedsLayout方法,視圖會在下一次繪制后更新。如果需要立即更新視圖,需要執(zhí)行l(wèi)ayoutIfNeeded方法
3、因?yàn)閘ayoutSubviews調(diào)用比較頻繁,因此若無特殊需求(文檔所述為執(zhí)行精確的子視圖布局時可使用),不用重寫layoutSubviews方法。
四、關(guān)于frame與bounds
眾所周知,在iOS UI控件中有兩個關(guān)于位置大小的非常重要的屬性,frame與bounds
UI控件的frame意為相對于該控件父視圖的位置,bounds意為相對于控件本身的位置。 frame、bounds均為結(jié)構(gòu)體CGRect,由CGPoint與CGSize組成,我們可以通過 frame.origin/bounds.origin 與frame.size/bounds.size來進(jìn)行返回控件左上角位置與大小。
通常給View添加動畫,可以直接操作Frame或者得到Layer設(shè)置隱式動畫。
那如果我們直接操作View的bounds會有什么情況出現(xiàn)呢?
有如下例子,有三個View,分別為RedView、BlueView、GreenView,RedView添加在當(dāng)前視圖控制器上,BlueView為RedView的子視圖,GreenView為BlueView的子視圖,坐標(biāo)分別為(10,10,200,200)、(10,10,150,150)、(10,10,100,100),其坐標(biāo)位置如下圖所示:
若修改BlueView的bounds為(0,10,150,150),那么會有什么情況出現(xiàn)呢?
可能我們通常移動View不會通過bounds而是frame,并且也知道bounds是相對于自身的坐標(biāo),修改其origin不會對其本身產(chǎn)生什么影響,但這就大錯特錯了,我們來看此情況的結(jié)果,三個View的展示情況變成了下圖所示:
BlueView位置并沒有什么變化,GreenView卻因?yàn)锽lueView的修改,其位置上移了10坐標(biāo)點(diǎn)!
我們來看下原因,因?yàn)檎{(diào)整里BlueView的bounds,導(dǎo)致BlueView相對于自己的坐標(biāo)上移了10坐標(biāo)點(diǎn),GreenView相對于其父視圖的位置也同樣上移了10坐標(biāo)點(diǎn)。對于GreenView,他的父視圖BlueView的左上角已經(jīng)不是(0,0),而是(0,10),因此會有上圖的結(jié)果。
總結(jié)
在CustomView中盡量使用frame來做某些操作,不出于特殊需求,不要修改bounds的origin屬性,會造成難以預(yù)期的Bug。(不會影響當(dāng)前視圖,但是會間接影響其子視圖)
以上就是IOS開發(fā)自定義view方法規(guī)范示例的詳細(xì)內(nèi)容,更多關(guān)于IOS開發(fā)自定義view的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
iOS 設(shè)置UILabel的行間距并自適應(yīng)高度的方法
下面小編就為大家?guī)硪黄猧OS 設(shè)置UILabel的行間距并自適應(yīng)高度的方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-04-04詳解關(guān)于iOS內(nèi)存管理的規(guī)則思考
本篇文章主要介紹了關(guān)于iOS內(nèi)存管理的規(guī)則思考,具有一定的參考價值,感興趣的小伙伴們可以參考一下。2016-12-12iOS中關(guān)于Taptic-Engine震動反饋的深入解析
這篇文章主要給大家介紹了關(guān)于iOS中關(guān)于Taptic-Engine震動反饋的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-11-11Flutter之TabBarView組件項(xiàng)目實(shí)戰(zhàn)示例
這篇文章主要為大家介紹了Flutter之TabBarView組件項(xiàng)目實(shí)戰(zhàn)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10iOS實(shí)現(xiàn)百度地圖拖拽后更新位置以及反編碼
百度地圖已經(jīng)開放了地圖API,大家可以很方便的調(diào)用地圖中的相應(yīng)數(shù)據(jù),并完成各項(xiàng)個性化的展示,下面這篇文章主要給大家介紹了關(guān)于iOS如何實(shí)現(xiàn)百度地圖拖拽后更新位置以及反編碼的相關(guān)資料,需要的朋友可以參考借鑒,下面來一起看看吧。2017-12-12iOS NSThread和NSOperation的基本使用詳解
下面小編就為大家分享一篇iOS NSThread和NSOperation的基本使用詳解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-01-01iOS11.3以下modal中input光標(biāo)錯位的解決方法
這篇文章主要介紹了iOS11.3以下modal中input光標(biāo)錯位的解決方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-12-12iOS系統(tǒng)緩存方面開發(fā)的相關(guān)基礎(chǔ)
這篇文章主要介紹了iOS系統(tǒng)緩存方面開發(fā)的相關(guān)基礎(chǔ),示例代碼基于傳統(tǒng)的Objective-C,需要的朋友可以參考下2015-10-10