如何創建iOS可視化控件

以前寫過一個文章《如何用Swift創建自定義iOS控件》,文章主要以Swift語言講解,圖個新鮮,其原理跟使用OC語言是一致的。這次再寫一個進階的文章,重點講一下如何制作可以和Xcode IDE交互的控件,以及如何與AutoLayout系統協作。

我們以一個自定義按鈕為出發點講解,創建方法和《如何用Swift創建自定義iOS控件》 類似,也是基于Nib來創建我們的控件。Demo項目在Github上這里, Demo還是有一定使用價值的,雖然還有很多需要完善的地方。

使用效果圖如下:

效果圖
效果圖

因為是進階教程,就不再一步一步手把手教了,重點闡述一些疑惑和踩坑的地方。

  1. 自定義控件如何與Xcode交互?

    • 屬性前用IBInspectable修飾(Swift的話是@IBInspectable)
    • 類聲明用IB_DESIGNABLE修飾(Swift的話是用@IBDesignable)
  2. -initWithFrame:, -initWithCoder:以及-awakeFromNib分別在什么時候調用。

  • -initWithFrame:是使用代碼創建控件的時候調用,-init:方法初始化也會調用,相當于調用[self initWithFrame:CGZeroRect]

  • -initWithCoder:是在Nib加載的時候調用的,其中包括在獨立的Xib文件,或者Storyboard文件,或者通過UINib類手寫代碼加載。

  • -awakeFromNib是Nib加載完畢,并且設置好所有的屬性之后調用。

因此,你看代碼就會看到在這三個方法里分別做三個不同的事情。

-initWithFrame:需要做完所有的工作,包括創建視圖createViews,初始化默認值setupDefaults(吐槽一下OC的屬性不能設置初始值,Swift就舒服很多很多),設置視圖表現setupViews。

-initWithCoder:則需要調用createViews和setupDefaults,如果在這里嘗試做setupViews的話,那么那些設計過程中修改了的屬性值就不會生效了,因為這時候的屬性值都還沒有初始化。

因此,只有在-awakeFromNib才做setupViews的工作。

  1. Xcode IDE怎么更新自定義控件的視圖表現的呢?

    最開始,我剛寫demo的時候用的辦法是為每個IBInspectable屬性編寫自定義set方法,每次設置屬性就重新執行一下setupViews方法。這個做法在設計階段不考慮執行效率的話還行。像我們的自定義按鈕,有10多個自定義屬性,如果運行時候每次設置屬性都執行一次setupViews,這樣的效率肯定是不能接受的。那么, 什么才是正道呢?答案是-prepareForInterfaceBuilder方法。

    Xcode需要更新設計UI的時候就會調用-prepareForInterfaceBuilder方法,并且,這個方法在運行時是不會被調用的,完美!另外,一個特別的宏TARGET_INTERFACE_BUILDER,這個宏只有在IDE執行代碼的時候才會被定義,利用這個宏,可以做一些針對設計階段執行的代碼,例如本例就針對設計初始化一個Title的默認值。

  2. 如何與AutoLayout系統對接?

    我們自定義控件內部的子視圖是在自己的Nib文件中做了AutoLayout的約束的。但是,自定義按鈕作為一個整體被外部時候的時候,外部也會有AutoLayout去約束它的大小和位置。這時候,AutoLayout系統需要知道一個重要的信息:你的控件內容有多大?

    當AutoLayout需要詢問控件的內容大小的時候,它會調用-intrinsicContentSize,我們的自定義控件根據各個子視圖(都是一些UILabel和UIImageView)的內容大小和間隔信息返回一個值就行了。注意:這個值必須是不依賴frame的。

    想想UILabel和UIButton的行為你大概就會明白這點了,這些控件你只需要定義位置約束,而不需要定義視圖大小約束就能完成約束了。當然,你也可以強制定義視圖大小約束,這時候就有內容大小和視圖大小約束優先級的問題,需要結合Hugging和Compression Resistance的優先級來確定最終內容大小和視圖大小了。

  3. 布局完成之后的位置調整

最初版本的Demo有個BUG,如果Title左右位置的圖標大小如果一樣,那么整體內容并沒有居中。出現這個BUG的原因是,我們把Title約束在整個內容視圖的中間。當左右兩個icon大小一致,或者Title左右的Span值不相同的情況下,需要把Title的中心位置偏移一下才能正確的表現整體內容居中的效果來。那么問題了,在哪里調整才合適呢?(怎么調整就只是算法問題,看代碼就能明白)。答案是-layoutSubviews 這個方法會在AutoLayout完成視圖布局之后調用,正式我們需要調整便宜的地方,因為,只有在視圖布局完成之后,我們才能知道各個子視圖正確的Frame大小。

  1. layoutSubviews里再修改約束不會導致無限循環嗎?

    這個問題,我沒有找到答案,只有調試能說明,答案是:不會?至于原理,沒有找到合適的官方文檔說明。有讀者知道的,請不吝賜教。

  2. 其他的一些小細節

    IconFont看上去真是個挺不錯的東西,資源小,矢量拉伸,可更改顏色,圖標資源豐富。再結合一個制作工具,把自己項目用到的圖標單獨抽取出來,只帶一個小字體文件就能搞定APP的所有圖標,非常的贊。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容