自定義控件一種方式爲純代碼,還有就是使用xib。數組
使用純代碼方式atom
1.建立繼承與UIView的子類,首先在initWithFrame: 把須要的控件加到view中。在這裏只能把控件加到view中,並不能設置各個子控件的尺寸。spa
爲何要在initWithFrame:方法而不是在init方法?設計
使用純代碼的方式建立自定義類,在之後的使用過程當中可能會使用init的方法建立,也可能使用initWithFrame:,不管那種方式最後都會調用initWithFrame:的方法。將加載控件的方法寫在這裏,能夠保證不管那種方式均可以建立成功。code
爲何要在initWithFrame:方法裏面只是將子控件加到view而不設置尺寸?對象
前面說過,建立view的方式有兩種,最後都會調用initWithFrame:方法。若是是使用init方法建立的,view的frame有多是不肯定的。繼承
若是是這種狀況,那麼在init方法中,frame是不肯定的,此時若是在initWithFrame:方法中設置尺寸,那麼各個子控件的尺寸都會是0,由於這個view的frame尚未設置。(能夠看到是在發送完init消息才設置的)接口
2. 在layoutSubviews方法中能夠完美解決上面的問題。第一次view將要顯示的時候會調用這個方法,以後當view的尺寸(不是位置)改變時,會調用這個方法。作用域
因此正常的作法應該是在initWithFrame:方法中建立子控件,注意此時子控件有可能只是一個局部變量,因此想要在layoutSubviews訪問到的話,通常須要建立這個子控件的對應屬性來指向它。it
@property (nonatomic, weak) UIButton *button; // 注意這裏使用weak就能夠,由於button已經被加入到self.view.subviews這個數組裏。 ... - (instancetype)initWithFrame: (CGRect)frame { if (self = [super initWithFrame: frame]) { UIButton *button = ... // 建立一個button [button setTitle: ...] // 設置button的屬性 [self.view addSubview: button]; // 將button加到view中,並不設置尺寸 self.button = button; //將self.button指向這個button保證在layoutSubviews中能夠訪問 UILabel *label = ... // 其餘的子控件同理 } }
這樣就能夠在layoutSubviews方法中訪問子控件,設置子控件的尺寸,此時view的frame已經肯定。
- (void)layoutSubviews { [super layoutSubviews]; // 注意,必定不要忘記調用父類的layoutSubviews方法! self.button.frame = ... // 設置button的frame self.label.frame = ... // 設置label的frame }
到這裏能夠實現自定義控件了。
3.同時,咱們還但願能夠給咱們的自定義控件數據,讓其顯示。
通常來講首先要將獲得的數據轉換成模型數據,而後給這個自定義控件傳入模型數據讓其顯示。
因此在這個自定義控件的頭文件,須要咱們設置接口以獲得別人傳入的數據。好比當前咱們有一個Book類,它有一個name屬性用於顯示名稱,有一個like屬性用於顯示多少人喜歡。如今咱們須要將Book的name顯示到自定義類的label子控件上,將Book的like顯示到自定義類的button子控件上。
首先在自定義類的頭文件中:
... @property (nonatomic, strong) Book *book; ...
在這裏咱們接收一個book做爲須要顯示的數據。
而後在自定義的實現文件中重寫book的setter方法:
- (void)setBook: (Book *)book { _book = book; // 注意在這個方法中,不寫這句也是沒有問題的,由於在下面的語句使用的是book而非self.book或_book,可是若是在其餘的方法中也想要訪問book這個屬性,那麼就須要寫上,不然self.book或_book會一直是nil(由於出了這個方法的做用域,book就銷燬了,若是再想訪問須要有其餘的引用指向它)。因此建議,要寫上這句。 [self.button setTitle: book.like forState...]; self.label = book.name; }
這樣,當咱們想要使用自定義類顯示數據時:
// 在控制器類的某個方法中: Book *book = self.books[index]; // 這裏指拿到books這個數據中的某個數據用於顯示 CYLView *view = [[CYLView alloc] initWithFrame: ...]; [self.view addSubview: view]; // 將自定義類加到view中 view.book = book; // 設置book的數據,此時會調用setter方法給各個控件設置數據
這樣一來就實現自定義類顯示數據的功能。並且將子控件封裝到自定義中,控制器只須要建立自定義類和給它數據,而不須要擔憂這個類內部是怎麼設計的,都有什麼控件,數據是如何安排的,因此當需求改變時,咱們的控制器有可能徹底不用改動,只需改變自定義類的內部就能夠。
總結:
initWithFrame:
中添加子控件。
layoutSubviews
中設置子控件frame。
對外設置數據接口,重寫setter方法給子控件設置顯示數據。
在view controller裏面使用init/initWithFrame:方法建立自定義類,而且給自定義類的frame賦值。
對自定義類對外暴露的數據接口進行賦值便可。
使用xib的方式能夠省去initWithFrame:
和layoutSubviews
中添加子控件和設置子控件尺寸的步驟,還有在view controller裏面設置view的frame,由於添加子控件和設置子控件的尺寸以及整個view的尺寸在xib中就已經完成。(注意整個view的位置尚未設置,須要在控制器裏面設置。)
咱們只需對外提供數據接口,重寫setter方法就能夠顯示數據。
注意要將xib中的類設置爲咱們的自定義類,這樣建立出來的纔是自定義類,而不是默認的父類。
固然,用xib這種方式是須要加載xib文件的。加載xib文件有兩種方法:
// 第一種方法(較爲經常使用) CYLView *view = [[[NSBundle mainBundle] loadNibNamed:@"CYLView" owner:nil options:nil] firstObject]; // CYLView表明CYLView.xib,表明CYLView這個類對應的xib文件。這個方法返回的是一個NSArray,咱們取第一個Object或最後一個(由於這個數組只有一個CYLView沒有其餘對象)就是須要加載的CYLView。 // 第二種方法 UINib *nib = [UINib nibWithNibName:@"CYLView" bundle:nil]; NSArray *objectArray = [nib instantiateWithOwner:nil options:nil]; CYLView *view = [objectArray firstObject];
xib文件中的控件能夠經過Control-Drag的方式在CYLView中進行連線,這樣CYLView是就能夠訪問這些控件。(能夠在setter方法中給這些控件賦值以顯示數據)
總結:
建立xib,在xib中拖入須要添加的控件並設置好尺寸。而且要將這個xib的Class設置爲咱們的自定義類。
經過IBOutlet的方式,將xib中的控件與自定義類進行關聯。
對外設置數據接口,重寫setter方法給子控件設置顯示數據。
在view controller類裏面加載xib文件就能夠獲得對應的類(這裏不須要再設置自定義類的frame,由於xib已經有了整個view的大小。只須要設置位置。),接着就能夠對類對外的數據接口賦值。
若是使用代碼的方式建立控件,那麼在建立時必定會調用initWithFrame:
方法;若是使用xib/storyboard方式建立控件,那麼在建立時必定會調用initWithCoder:
方法。
在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。
事實上使用xib建立自定義控件,咱們能夠將加載xib的過程封裝到自定義的類中,只對外暴露一個初始化方法,這樣外界就不知道內部是如何建立的自定義控件了。
好比在CYLView.h中提供一個類工廠方法:
+ (instancetype)viewWithBook: (Book *)book;
而後在CYLView.m中實現這個方法:
+ (instancetype)viewWithBook: (Book *)book { CYLView *view = [[[NSBundle mainBundle] loadNibNamed: NSStringFromClass(self) owner: nil opetions: nil] firstObject]; view.book = book; return view; }
這樣外界只需用viewWithBook:
方法傳入一個book,就能夠建立一個CYLView的對象,而具體是怎麼建立的,只有CYLView才知道。
若是咱們想,不管是經過代碼的方式,仍是經過xib的方式,都會初始化一些值,那麼咱們能夠將初始化的代碼抽到一個方法裏面,而後在initWithFrame:
方法和awakeFromNib
方法中分別調用這個方法。
關於爲何是awakeFromNib
前面已經說了:
經過xib的方式建立的自定義控件,須要設置IBOutlet屬性,雖然會調用initWithCoder:
方法,可是調用這個的方法的時候IBOutlet屬性還未設置好,因此在這個方法中訪問屬性將會是nil。而在awakeFromNib
中,IBOutlet已經初始化完畢,因此在這個方法中初始化不會失敗。
若是經過initWithFrame:
方法,說明是經過代碼建立的自定義控件,它的屬性並非IBOutlet的,因此不存在未完成IBOutlet的屬性未初始化完這種狀況。因此在initWithFrame:
方法中訪問一些屬性是沒有問題的。可是應該注意,若是是經過init
方法建立的自定義控件也會調用initWithFrame:
方法,可是此時的self.frame
是沒有被賦值的(在掉用這個方法的時候並無設置控件的大小),若是這種狀況下使用self.frame
是沒有值的。注意這種狀況。
文/ForeverYoung21(簡書做者) 原文連接:http://www.jianshu.com/p/7e47da62899c 著做權歸做者全部,轉載請聯繫做者得到受權,並標註「簡書做者」。