使用純代碼的方式
-
一般來說我們的自定義類繼承自UIView票髓,首先在
initWithFrame:
方法中將需要的子控件加入view中深夯。注意葱淳,這里只是加入到view中沮协,并沒有設(shè)置各個(gè)子控件的尺寸辆飘。為什么要在initWithFrame:方法而不是在init方法啦辐?
因?yàn)槭褂眉兇a的方式創(chuàng)建自定義類,在以后使用的時(shí)候可能使用
init
方法創(chuàng)建蜈项,也有可能使用initWithFrame:
方法創(chuàng)建芹关,但是無論哪種方式,最后都會(huì)調(diào)用到initWithFrame:
方法紧卒。在這個(gè)方法中創(chuàng)建子控件侥衬,可以保證無論哪種方式都可以成功創(chuàng)建。為什么要在initWithFrame:方法里面只是將子控件加到view而不設(shè)置尺寸跑芳?
前面已經(jīng)說過轴总,兩種方式最后都會(huì)調(diào)用到
initWithFrame:
方法。如果使用init
方法創(chuàng)建博个,那么這個(gè)view的frame有可能是不確定的:CYLView *view = [[CYLView alloc] init]; view.frame = CGRectMake(0, 0, 100, 100); ...
如果是這種情況怀樟,那么在
init
方法中,frame是不確定的盆佣,此時(shí)如果在initWithFrame:
方法中設(shè)置尺寸往堡,那么各個(gè)子控件的尺寸都會(huì)是0,因?yàn)檫@個(gè)view的frame還沒有設(shè)置罪塔。(可以看到是在發(fā)送完init消息才設(shè)置的)所以我們應(yīng)該保證view的frame設(shè)置完才會(huì)設(shè)置它的子控件的尺寸投蝉。
-
在
layoutSubviews
方法中就可以達(dá)到這個(gè)目的。第一次view__將要顯示__的時(shí)候會(huì)調(diào)用這個(gè)方法征堪,之后當(dāng)view的尺寸(不是位置)改變時(shí)瘩缆,會(huì)調(diào)用這個(gè)方法。所以正常的做法應(yīng)該是在
initWithFrame:
方法中創(chuàng)建子控件佃蚜,注意此時(shí)子控件有可能只是一個(gè)局部變量庸娱,所以想要在layoutSubviews
訪問到的話,一般需要?jiǎng)?chuàng)建這個(gè)子控件的對(duì)應(yīng)屬性來指向它谐算。@property (nonatomic, weak) UIButton *button; // 注意這里使用weak就可以熟尉,因?yàn)閎utton已經(jīng)被加入到self.view.subviews這個(gè)數(shù)組里。 ... - (instancetype)initWithFrame: (CGRect)frame { if (self = [super initWithFrame: frame]) { UIButton *button = ... // 創(chuàng)建一個(gè)button [button setTitle: ...] // 設(shè)置button的屬性 [self.view addSubview: button]; // 將button加到view中洲脂,并不設(shè)置尺寸 self.button = button; //將self.button指向這個(gè)button保證在layoutSubviews中可以訪問 UILabel *label = ... // 其他的子控件同理 } }
這樣我們就可以在
layoutSubviews
中訪問子控件斤儿,設(shè)置子控件的尺寸剧包,因?yàn)榇藭r(shí)view的frame已經(jīng)確定。- (void)layoutSubviews { [super layoutSubviews]; // 注意往果,一定不要忘記調(diào)用父類的layoutSubviews方法疆液! self.button.frame = ... // 設(shè)置button的frame self.label.frame = ... // 設(shè)置label的frame }
經(jīng)過以上的步驟,就可以實(shí)現(xiàn)自定義控件陕贮。
-
同時(shí)堕油,我們還希望可以給我們的自定義控件數(shù)據(jù),讓其顯示肮之。
一般來說首先要將得到的數(shù)據(jù)轉(zhuǎn)換成模型數(shù)據(jù)掉缺,然后給這個(gè)自定義控件傳入模型數(shù)據(jù)讓其顯示。
所以在這個(gè)自定義控件的頭文件戈擒,需要我們設(shè)置接口以得到別人傳入的數(shù)據(jù)眶明。比如當(dāng)前我們有一個(gè)Book類,它有一個(gè)name屬性用于顯示名稱峦甩,有一個(gè)like屬性用于顯示多少人喜歡∽咐矗現(xiàn)在我們需要將Book的name顯示到自定義類的label子控件上现喳,將Book的like顯示到自定義類的button子控件上凯傲。
首先在自定義類的頭文件中:
... @property (nonatomic, strong) Book *book; ...
在這里我們接收一個(gè)book作為需要顯示的數(shù)據(jù)。
然后在自定義的實(shí)現(xiàn)文件中重寫book的setter方法:
- (void)setBook: (Book *)book { _book = book; // 注意在這個(gè)方法中嗦篱,不寫這句也是沒有問題的冰单,因?yàn)樵谙旅娴恼Z句使用的是book而非self.book或_book,但是如果在其他的方法中也想要訪問book這個(gè)屬性灸促,那么就需要寫上诫欠,否則self.book或_book會(huì)一直是nil(因?yàn)槌隽诉@個(gè)方法的作用域,book就銷毀了浴栽,如果再想訪問需要有其他的引用指向它)荒叼。所以建議,要寫上這句典鸡。 [self.button setTitle: book.like forState...]; self.label = book.name; }
這樣被廓,當(dāng)我們想要使用自定義類顯示數(shù)據(jù)時(shí):
// 在控制器類的某個(gè)方法中: Book *book = self.books[index]; // 這里指拿到books這個(gè)數(shù)據(jù)中的某個(gè)數(shù)據(jù)用于顯示 CYLView *view = [[CYLView alloc] initWithFrame: ...]; [self.view addSubview: view]; // 將自定義類加到view中 view.book = book; // 設(shè)置book的數(shù)據(jù),此時(shí)會(huì)調(diào)用setter方法給各個(gè)控件設(shè)置數(shù)據(jù)
這樣一來就實(shí)現(xiàn)自定義類顯示數(shù)據(jù)的功能萝玷。而且將子控件封裝到自定義中嫁乘,控制器只需要?jiǎng)?chuàng)建自定義類和給它數(shù)據(jù),而不需要擔(dān)心這個(gè)類內(nèi)部是怎么設(shè)計(jì)的球碉,都有什么控件蜓斧,數(shù)據(jù)是如何安排的,所以當(dāng)需求改變時(shí)睁冬,我們的控制器有可能完全不用改動(dòng)挎春,只需改變自定義類的內(nèi)部就可以。
?
總結(jié):
initWithFrame:
中添加子控件。layoutSubviews
中設(shè)置子控件frame直奋。對(duì)外設(shè)置數(shù)據(jù)接口狼荞,重寫setter方法給子控件設(shè)置顯示數(shù)據(jù)。
在view controller里面使用init/initWithFrame:方法創(chuàng)建自定義類帮碰,并且給自定義類的frame賦值相味。
-
對(duì)自定義類對(duì)外暴露的數(shù)據(jù)接口進(jìn)行賦值即可。
?
使用xib方式
使用xib的方式可以省去
initWithFrame:
和layoutSubviews
中添加子控件和設(shè)置子控件尺寸的步驟殉挽,還有在view controller里面設(shè)置view的frame丰涉,因?yàn)樘砑幼涌丶驮O(shè)置子控件的尺寸以及整個(gè)view的尺寸在xib中就已經(jīng)完成。(注意整個(gè)view的位置還沒有設(shè)置斯碌,需要在控制器里面設(shè)置一死。)我們只需對(duì)外提供數(shù)據(jù)接口,重寫setter方法就可以顯示數(shù)據(jù)傻唾。
注意要將xib中的類設(shè)置為我們的自定義類投慈,這樣創(chuàng)建出來的才是自定義類,而不是默認(rèn)的父類冠骄。
-
當(dāng)然伪煤,用xib這種方式是需要加載xib文件的。加載xib文件有兩種方法:
// 第一種方法(較為常用) CYLView *view = [[[NSBundle mainBundle] loadNibNamed:@"CYLView" owner:nil options:nil] firstObject]; // CYLView代表CYLView.xib凛辣,代表CYLView這個(gè)類對(duì)應(yīng)的xib文件抱既。這個(gè)方法返回的是一個(gè)NSArray,我們?nèi)〉谝粋€(gè)Object或最后一個(gè)(因?yàn)檫@個(gè)數(shù)組只有一個(gè)CYLView沒有其他對(duì)象)就是需要加載的CYLView扁誓。 // 第二種方法 UINib *nib = [UINib nibWithNibName:@"CYLView" bundle:nil]; NSArray *objectArray = [nib instantiateWithOwner:nil options:nil]; CYLView *view = [objectArray firstObject];
xib文件中的控件可以通過Control-Drag的方式在CYLView中進(jìn)行連線防泵,這樣CYLView是就可以訪問這些控件。(可以在setter方法中給這些控件賦值以顯示數(shù)據(jù))
總結(jié):
創(chuàng)建xib蝗敢,在xib中拖入需要添加的控件并設(shè)置好尺寸捷泞。并且要將這個(gè)xib的Class設(shè)置為我們的自定義類。
通過IBOutlet的方式寿谴,將xib中的控件與自定義類進(jìn)行關(guān)聯(lián)锁右。
對(duì)外設(shè)置數(shù)據(jù)接口,重寫setter方法給子控件設(shè)置顯示數(shù)據(jù)拭卿。
-
在view controller類里面加載xib文件就可以得到對(duì)應(yīng)的類(這里不需要再設(shè)置自定義類的frame骡湖,因?yàn)閤ib已經(jīng)有了整個(gè)view的大小。只需要設(shè)置位置峻厚。)响蕴,接著就可以對(duì)類對(duì)外的數(shù)據(jù)接口賦值。
?
補(bǔ)充
如果使用代碼的方式創(chuàng)建控件惠桃,那么在創(chuàng)建時(shí)一定會(huì)調(diào)用
initWithFrame:
方法浦夷;如果使用xib/storyboard方式創(chuàng)建控件辖试,那么在創(chuàng)建時(shí)一定會(huì)調(diào)用initWithCoder:
方法。-
在
initWithCoder:
里面訪問屬性劈狐,比如self.button
罐孝,會(huì)發(fā)現(xiàn)它是nil的,因?yàn)榇藭r(shí)自定義控件正在初始化肥缔,self.button可能還未賦值(self.button是一個(gè)IBOutlet莲兢,IBOutlet本質(zhì)上就相當(dāng)于Xcode找到這個(gè)對(duì)應(yīng)的屬性,然后UIButton *button = … , [self.view addSubview: button]這種操作续膳,而這一切的操作都是相當(dāng)于在CYLView *view = [[CYLView alloc] initWithCoder: nil]方法之后執(zhí)行的改艇。上面的代碼就相當(dāng)于用代碼的方式實(shí)現(xiàn)Xcode在storyboard中加載CYLView),所以如果在這個(gè)方法中進(jìn)行初始化操作是可能會(huì)失敗的坟岔。所以建議在
awakeFromNib
方法中進(jìn)行初始化的額外操作谒兄。因?yàn)?code>awakeFromNib是在初始化完成后調(diào)用,所以在這個(gè)方法里面訪問屬性(IBOutlet)就可以保證不為nil社付。 -
事實(shí)上使用xib創(chuàng)建自定義控件承疲,我們可以將加載xib的過程封裝到自定義的類中,只對(duì)外暴露一個(gè)初始化方法鸥咖,這樣外界就不知道內(nèi)部是如何創(chuàng)建的自定義控件了燕鸽。
比如在CYLView.h中提供一個(gè)類工廠方法:
+ (instancetype)viewWithBook: (Book *)book;
然后在CYLView.m中實(shí)現(xiàn)這個(gè)方法:
+ (instancetype)viewWithBook: (Book *)book { CYLView *view = [[[NSBundle mainBundle] loadNibNamed: NSStringFromClass(self) owner: nil opetions: nil] firstObject]; view.book = book; return view; }
這樣外界只需用
viewWithBook:
方法傳入一個(gè)book,就可以創(chuàng)建一個(gè)CYLView的對(duì)象扛或,而具體是怎么創(chuàng)建的绵咱,只有CYLView才知道。 -
如果我們想熙兔,無論是通過代碼的方式,還是通過xib的方式艾恼,都會(huì)初始化一些值住涉,那么我們可以將初始化的代碼抽到一個(gè)方法里面,然后在
initWithFrame:
方法和awakeFromNib
方法中分別調(diào)用這個(gè)方法钠绍。關(guān)于為什么是
awakeFromNib
前面已經(jīng)說了:通過xib的方式創(chuàng)建的自定義控件舆声,需要設(shè)置IBOutlet屬性,雖然會(huì)調(diào)用
initWithCoder:
方法柳爽,但是調(diào)用這個(gè)的方法的時(shí)候IBOutlet屬性還未設(shè)置好媳握,所以在這個(gè)方法中訪問屬性將會(huì)是nil。而在awakeFromNib
中磷脯,IBOutlet已經(jīng)初始化完畢蛾找,所以在這個(gè)方法中初始化不會(huì)失敗。如果通過
initWithFrame:
方法赵誓,說明是通過代碼創(chuàng)建的自定義控件打毛,它的屬性并不是IBOutlet的柿赊,所以不存在未完成IBOutlet的屬性未初始化完這種情況。所以在initWithFrame:
方法中訪問一些屬性是沒有問題的幻枉。但是應(yīng)該注意碰声,如果是通過init
方法創(chuàng)建的自定義控件也會(huì)調(diào)用initWithFrame:
方法,但是此時(shí)的self.frame
是沒有被賦值的(在掉用這個(gè)方法的時(shí)候并沒有設(shè)置控件的大邪靖Α)胰挑,如果這種情況下使用self.frame
是沒有值的。注意這種情況椿肩。?