iOS開發-自定義控件的方式及注意

使用純代碼的方式

  1. 一般來說我們的自定義類繼承自UIView,首先在initWithFrame:方法中將需要的子控件加入view中。注意,這里只是加入到view中,并沒有設置各個子控件的尺寸。

    為什么要在initWithFrame:方法而不是在init方法?

    因為使用純代碼的方式創建自定義類,在以后使用的時候可能使用init方法創建,也有可能使用initWithFrame:方法創建,但是無論哪種方式,最后都會調用到initWithFrame:方法。在這個方法中創建子控件,可以保證無論哪種方式都可以成功創建。

    為什么要在initWithFrame:方法里面只是將子控件加到view而不設置尺寸?

    前面已經說過,兩種方式最后都會調用到initWithFrame:方法。如果使用init方法創建,那么這個view的frame有可能是不確定的:

    CYLView *view = [[CYLView alloc] init];
    view.frame = CGRectMake(0, 0, 100, 100);
    ...
    

    如果是這種情況,那么在init方法中,frame是不確定的,此時如果在initWithFrame:方法中設置尺寸,那么各個子控件的尺寸都會是0,因為這個view的frame還沒有設置。(可以看到是在發送完init消息才設置的)

    所以我們應該保證view的frame設置完才會設置它的子控件的尺寸。

  2. layoutSubviews方法中就可以達到這個目的。第一次view__將要顯示__的時候會調用這個方法,之后當view的尺寸(不是位置)改變時,會調用這個方法。

    所以正常的做法應該是在initWithFrame:方法中創建子控件,注意此時子控件有可能只是一個局部變量,所以想要在layoutSubviews訪問到的話,一般需要創建這個子控件的對應屬性來指向它。

    @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方法給各個控件設置數據
    

    這樣一來就實現自定義類顯示數據的功能。而且將子控件封裝到自定義中,控制器只需要創建自定義類和給它數據,而不需要擔心這個類內部是怎么設計的,都有什么控件,數據是如何安排的,所以當需求改變時,我們的控制器有可能完全不用改動,只需改變自定義類的內部就可以。

    ?

總結:

  1. initWithFrame:中添加子控件。

  2. layoutSubviews中設置子控件frame。

  3. 對外設置數據接口,重寫setter方法給子控件設置顯示數據。

  4. 在view controller里面使用init/initWithFrame:方法創建自定義類,并且給自定義類的frame賦值。

  5. 對自定義類對外暴露的數據接口進行賦值即可。

    ?

使用xib方式

  1. 使用xib的方式可以省去initWithFrame:layoutSubviews中添加子控件和設置子控件尺寸的步驟,還有在view controller里面設置view的frame,因為添加子控件和設置子控件的尺寸以及整個view的尺寸在xib中就已經完成。(注意整個view的位置還沒有設置,需要在控制器里面設置。)

  2. 我們只需對外提供數據接口,重寫setter方法就可以顯示數據。

  3. 注意要將xib中的類設置為我們的自定義類,這樣創建出來的才是自定義類,而不是默認的父類。

  4. 當然,用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];
    
  5. xib文件中的控件可以通過Control-Drag的方式在CYLView中進行連線,這樣CYLView是就可以訪問這些控件。(可以在setter方法中給這些控件賦值以顯示數據)

總結:

  1. 創建xib,在xib中拖入需要添加的控件并設置好尺寸。并且要將這個xib的Class設置為我們的自定義類。

  2. 通過IBOutlet的方式,將xib中的控件與自定義類進行關聯。

  3. 對外設置數據接口,重寫setter方法給子控件設置顯示數據。

  4. 在view controller類里面加載xib文件就可以得到對應的類(這里不需要再設置自定義類的frame,因為xib已經有了整個view的大小。只需要設置位置。),接著就可以對類對外的數據接口賦值。

    ?

補充

  1. 如果使用代碼的方式創建控件,那么在創建時一定會調用initWithFrame:方法;如果使用xib/storyboard方式創建控件,那么在創建時一定會調用initWithCoder:方法。

  2. 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。

  3. 事實上使用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才知道。

  4. 如果我們想,無論是通過代碼的方式,還是通過xib的方式,都會初始化一些值,那么我們可以將初始化的代碼抽到一個方法里面,然后在initWithFrame:方法和awakeFromNib方法中分別調用這個方法。

    關于為什么是awakeFromNib前面已經說了:

    通過xib的方式創建的自定義控件,需要設置IBOutlet屬性,雖然會調用initWithCoder:方法,但是調用這個的方法的時候IBOutlet屬性還未設置好,所以在這個方法中訪問屬性將會是nil。而在awakeFromNib中,IBOutlet已經初始化完畢,所以在這個方法中初始化不會失敗。

    如果通過initWithFrame:方法,說明是通過代碼創建的自定義控件,它的屬性并不是IBOutlet的,所以不存在未完成IBOutlet的屬性未初始化完這種情況。所以在initWithFrame:方法中訪問一些屬性是沒有問題的。但是應該注意,如果是通過init方法創建的自定義控件也會調用initWithFrame:方法,但是此時的self.frame是沒有被賦值的(在掉用這個方法的時候并沒有設置控件的大小),如果這種情況下使用self.frame是沒有值的。注意這種情況。

    ?

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,818評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,185評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,656評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,647評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,446評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,951評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,041評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,189評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,718評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,800評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,419評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,420評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,755評論 2 371

推薦閱讀更多精彩內容