如何寫好一個自定義View

前言

對於iOS開發來講,寫一個自定義view,或者恰當地使用tableview基本上能夠算的上是「行活」。可是看過一些同窗寫的自定義控件後,有時感受彷佛寫的不夠好,雖然能夠正常工做,可是在可拓展性、易用性、以及穩定性上都有所欠缺。因此我打算寫一個系列,就叫作如何寫好xxx,就總結下我認爲的好的寫法應該是什麼樣的,這篇即是這系列的第一篇。html

固然受視野和水平所限,文章中提到的一些東西並不必定是最優解,很是歡迎你們提出不一樣的意見,討論後共同成長!ios

目標

  • 使用方式多樣
    • 純代碼中使用
    • Xib/storyboard中使用
  • 使用的易用性
    • 儘可能簡單的接口設計
    • 儘可能少的暴露實現
    • 對異常狀況的處理

實現

初始化方法

這裏咱們大可借鑑一下UIKit中系統的UI組件是如何設計本身的初始化方法的。git

UIKit中初始化方法大概分爲兩類,github

  • 繼承自父類的Designated initializerobjective-c

    • initWithFrame
    • initWithCoder(不是全部的UI類都繼承UIView,例如繼承NSObject的UIBarItem,這些就沒有initWithFrame方法)
  • Convenience Initializer,例如UITabBarItem中的算法

    - (instancetype)initWithTitle:(nullable NSString *)title image:(nullable UIImage *)image tag:(NSInteger)tag;
    - (instancetype)initWithTitle:(nullable NSString *)title image:(nullable UIImage *)image selectedImage:(nullable UIImage *)selectedImage NS_AVAILABLE_IOS(7_0);
    - (instancetype)initWithTabBarSystemItem:(UITabBarSystemItem)systemItem tag:(NSInteger)tag;
    複製代碼

    等方法swift

首先,咱們要搞清楚什麼是Designated initializer和Convenience Initializer。app

  • Designated initializer,初始化類必須有的屬性
  • Convenience Initializer,提供便利的初始化方法,根據須要爲某些屬性提供默認值,方法內部實現最終仍是會調用Designated initializer;

其次,爲何UIView的子類都會有兩個Designated initializer呢?這裏就是咱們以前提到的,View的兩種使用方法,Xib/storyboard,和純代碼。iview

實現Designated initializer

爲了既能知足純代碼的方式,又能使用Xib的方式,咱們須要實現CustomView的兩個Designated initializeride

並且在swift中,initWithCoder已經被標記爲required,因此必需要實現啦

在實現這兩個方式時,主要作的就是添加子view,以及提供默認值

提供Convenience Initializer

例如UIImageView,他就提供了initWithImage 這個Convenience Initializer。

使用Convenience Initializer的好處也是顯而易見的,能讓類的使用者很清楚的知道我應該如何正確的初始化這個類。並且會對必需的屬性提供默認值,既能極大的避免了調用者只調用init,致使該實例並不能正常工做,又能在不少屬性時,提供一個簡單的初始化方法。

內部子view佈局的實現

frame or autoLayout?

若是使用frame,咱們須要保證custom view本身的size發生變化的時候,subviews可以自動變化,而不是保持原有的frame。(autoresizingMask,autoresizesSubviews)

若是使用autoLayout,就不存在上面的問題,惟一一個須要考慮的問題即是性能了。經過WWDC也能夠知道,雖然蘋果對於autolayout一再優化,仍然在多視圖情境下,性能遠不如frame

個人觀點 :若是頁面層級不復雜,性能差異也不大,我仍是傾向使用AutoLayout,畢竟算frame也是比較麻煩,並且代碼可讀性也要比AutoLayout差不少

構建視圖

這時須要解釋幾個很重要的方法,以及何時須要使用

  • -(void)drawRect:(CGRect)rect
    • 使用場景:須要使用Core Graphics或者UIKit繪製頁面時,若是是使用已有UI控件addsubview組合本身的view,則不須要重寫這個方法
    • 被調用時機:view首次顯示的時候,或者某個事件致使了view須要更新,不要直接手動調用。若是須要重繪,調用 setNeedsDisplay 或者 setNeedsDisplayInRect:
    • 參數說明:view須要更新的範圍,若是是連續的繪製,那麼rect可能只是view的一部分
  • -(void)layoutSubviews
    • 使用場景:只有當autoresizing和約束不能知足你的需求時,才重寫layoutSubviews來提供更精確的佈局
    • 被調用時機:不要直接手動調用,調用setNeedsLayout來更新約束,若是須要當即更新約束,那麼調用 layoutIfNeeded
  • -(void)updateConstraints;
    • 使用場景:爲了優化約束的變化,須要提前改變約束,或者產生大量冗餘修改時。
    • 被調用時機:不要直接手動調用,在view須要修改約束的時候,調用setNeedsUpdateConstraints
    • 注意事項:
      • 在實現的最後,調用[Super updateConstraints]
      • 不要在方法實現中調用setNeedsUpdateConstraints,會產生循環

接口的設計

接口設計儘可能遵循Effective Objective-C 2.0中的建議,好比必須暴露的屬性儘可能爲readonly,內部實現的私有方法沒必要暴露出去。在設計接口的時候,時刻要想着,這個方法,這個屬性真的有必要讓別人知道嗎?這個方法真正的目的是什麼?總之,儘可能遵循Keep It Simple, Stupid就對了。

線程管理

全部UI的操做都應該在主線程進行,這須要咱們在涉及到UI變更的方法中,確保是主線程,而不依賴使用者

一個簡單的例子

TCZoomingImageView是基於UIScrollView和UIImageView作一個可縮放的View,實現很是的簡單,僅做爲一個簡單的例子,拋磚引玉。

GitHub地址:TCZoomingImageView

參考資料

UIView Document From Apple

View Programming Guide for iOS

iOS 建立對象的姿式

Object Initialization

從 Auto Layout 的佈局算法談性能

WWDC:High Performance Auto Layout

WWDC:Mysteries of Auto Layout, Part 1

[WWDC:Mysteries of Auto Layout, Part 2

相關文章
相關標籤/搜索