本文為投稿文章,作者:minggo(簡書)
我們好像慢慢地習慣了“理想很豐滿,現實很骨感”這樣順序這樣的轉折這樣常態,那麼如果是“現實很豐滿,理想很骨感”,我們能接受嗎?現實豐滿可以,但是理想很骨感那就不要將就了。就像薛之謙希望是能通過“丑八怪 呀啊呀啊呀哎呀”來唱紅的自己,而不是上綜藝做直男直到沒朋友的諧星來笑紅自己卻跟他的歌關系不大。
蘋果開發中使用的XCode也有這樣的“現實豐滿,理想骨感”例子,蘋果公司在2011年就推出了UIStoryboard技術,到現在已經6年了。蘋果還不在xcode的Interface Builder上直接提供修改控件的圓角,邊框設置,而是提供IBDesignable/IBInspectable這樣的技術讓這些重復簡單的工作由開發者來實現圓角外框,最後category上使用IBDesignable/IBInspectable卻不能直接現實想要的結果。
那邊我們來體驗一下薛之謙的“人紅歌不紅”的“現實很豐滿,理想很骨感”感覺。
一、我們想要很簡單
在Storyboard中的所有控件能通過Interface builder直接設置最基本圓角,邊框和邊框顏色屬性,表達理想的Storyboard能顯示出我要的渲染效果,而不是編寫代碼後只能在運行在手機或模擬器時才出現我們想要的效果。就像薛之謙想通唱歌紅了自己,簡單明了。
二、基本概念
IB_DESIGNABLE的宏的功能就是讓XCode動態渲染出該類圖形化界面。UIView 或 NSView使用IB_DESIGNABLE宏聲明時候,就是讓Interface Builder知道它應該在UIStoryboard或者Xib中畫布上直接渲染視圖,不需要等到編譯運行後就能預先展示出來效果 。
IBInspectable修飾屬性,可以是用戶自定義的運行時屬性,讓支持KVC的屬性能夠在Attribute Inspector中配置。
三、使用方式
1.IB_DESIGNABLE放在@interface或者@implement都可以,申明這個類在XCode直接看到渲染的效果。
IB_DESIGNABLE @interface IBDesignableImageView : UIImageView
2.IBInspectable 修飾屬性,使屬性能在XCode中直接設置。
@property(nonatomic,assign) IBInspectable CGFloat cornerRadius;
四、普通類繼承關系實現渲染效果
根據第三所列出的2個關鍵點,詳細具體實現:
1.自定義IBDesignableImageView 繼承UIImageView
#importIB_DESIGNABLE @interface IBDesignableImageView : UIImageView @property(nonatomic,assign) IBInspectable CGFloat cornerRadius; @end
2.接著在IBDesignableImageView.m文件實現下set方法
#import "IBDesignableImageView.h" @implementation IBDesignableImageView -(void)setCornerRadius:(CGFloat)cornerRadius{ _cornerRadius = cornerRadius;//不要使用self.cornerRadius = cornerRadius;這樣會死循環 self.layer.masksToBounds = YES; self.layer.cornerRadius = cornerRadius; } @end
3.接著,XCode中Customer Class選擇IBDesignableImageView。
4.最後,interface builder屬性欄中設置你剛剛屬性。
類繼承關系實現效果圖
主要就是以上四步基本滿足一下下storyboard成功展示一種View實時渲染效果的快感,猶如薛之謙嘗試到一首《丑八怪》火一陣子,爽一陣子的快感,並且不會讓人誤認為這是趙全的《我很丑可是我很溫柔》的續本。
五、UIView的Category實現渲染效果
嘗到了好處,自然想到實現通用的方式給需要控件都加上interface builder可設置相關屬性。所以,我們自認為UIView其他子View父類,那麼如果UIView可是直接在XCode上設置屬性實現渲染,那麼其他子View隨之得到渲染效果。
1.我們最想看到的結果是
我們最想看到結果是,UIImageView、UITextField、UIButton等等都能如願的被interface builder直接修改相關圓角,邊框等等屬性。噔噔,我們腦海浮現以下畫面:
最理想狀態
2.編寫UIView的Category類UIView+MGO.h
#importIB_DESIGNABLE @interface UIView (MGO) @property(nonatomic,assign) IBInspectable CGFloat cornerRadius; @property(nonatomic,assign) IBInspectable CGFloat borderWidth; @property(nonatomic,assign) IBInspectable UIColor *borderColor; @property(nonatomic,assign) IBInspectable CGFloat defineValue; @end
3.編寫UIView+MGO.m中的實現
#import "UIView+MGO.h" #import@implementation UIView (MGO) -(void)setCornerRadius:(CGFloat)cornerRadius{ self.layer.masksToBounds = YES; self.layer.cornerRadius = cornerRadius; } -(void)setBorderColor:(UIColor *)borderColor{ self.layer.borderColor = borderColor.CGColor; } -(void)setBorderWidth:(CGFloat)borderWidth{ self.layer.borderWidth = borderWidth; } -(void)setDefineValue:(CGFloat)defineValue{ objc_setAssociatedObject(self, @selector(defineValue), @(defineValue),OBJC_ASSOCIATION_ASSIGN); } -(CGFloat)cornerRadius{ return self.layer.cornerRadius; } -(CGFloat)borderWidth{ return self.layer.borderWidth; } -(UIColor *)borderColor{ return [UIColor colorWithCGColor:self.layer.borderColor]; } -(CGFloat)defineValue{ return [objc_getAssociatedObject(self, @selector(defineValue)) floatValue]; } @end
這個.m的實現類,有幾個知識點要注意:
1)category中添加IBInspectable修飾屬性,必須帶有set和get的方法,不然編譯都通過。
2)KVC觀察屬性值變化,從而得到即時刷新效果。
3).ategory中自定義屬性如上邊defineValue屬性,使用runtime方式賦值。所以會有
objc_setAssociatedObject(self, @selector(defineValue), @(defineValue),OBJC_ASSOCIATION_ASSIGN);
這樣的代碼。_defineValue = defineValue;這樣也是行,這樣是有語法問題的。
詳細的runtime和KVC知識點可以查看《談Runtime機制和使用的整體化梳理》和《談KVC、KVO(重點觀察者模式)機制編程》
4.在interface builder設置相關屬性
1)屬性欄可以看可設置圓角,邊框寬度等
設置UIView(MGO定義出來屬性)
2)Runtime Attribute欄中也能自動生成剛剛設置的屬性值
Runtime Attribute顯示屬性值
5.查看storyboard上的頭像有沒有顯示設置的屬性值
UIView(MGO)展示的效果
Oh。。。my god!storyboard上一點效果也沒顯示出來,反而模擬器運行效果確實理想的狀態。活生生的“現實很豐滿,理想很骨感”表現得淋漓盡致。難道要硬著頭皮瞬間退回到解放前,重復不斷的寫繼承代碼。。。打死也不,,,告訴你淘寶發過來的驗證碼!
六、曲線救國處理Category不顯示效果問題
曲線救國生活中可以,國就是救那麼一兩次,沒國了也沒辦法。程序中,每時每刻都在救國,明擺著這欺負人嘛!不過,國我們還是得救的,歌薛之謙還是要唱的,先找到兩者的微妙關系,串成曲線才行。於是乎,薛之謙將段子手的頭銜發揮到極致,參加各大綜藝,《火星情報站》《極限挑戰》《大學生來了》等等,趁機唱唱“丑八怪 呀啊呀啊哎呀”,道理就明了了,就是死活出現在人面前晃來晃去。
喲,那會不會interface builder死活要看到UIView的自定義子類的繼承類,設置在Customer Class中才行啊。試試?
1.編寫一個 空白MGOImageView繼承UIImageView。
#import@interface MGOImageView : UIImageView @end
2.MGOImageView.m文件什麼也不干,就是等機會對接UIView(MGO)自定義屬性。
#import "MGOImageView.h" @implementation MGOImageView @end
3.選中storyboard中的UIImageView在Customer Class選擇 MGOImageView。
編寫空白MGOImageView嘗試
噢,,,了個天啊!storyboard上一點效果都沒有少顯示出來,反而嚇到了現實中的我。那就趁熱吃豆腐了,把其他控件都寫一個對應空白的子類,設置在customer class上。
其他控件都添加對應的子類
好像幸福就是來得那麼突然,傷心的小船說翻就翻了。會不會有更大幸福,於是乎,小明在google上搜索“IBDesignable/IBInspectable在Category中沒有效果”。看了兩三頁搜索結果,最後比較接近的是Cocachina中也有一個沒有人回復的相關帖子,和另外一篇跟我的發現一樣要寫一個空白的子類設置在Customer Class 中才行。
七、對比總結
經過以上一系列折騰,最後的結果確認令我覺得“不完美的IBDesignable/IBInspectable”。有些對XCode,Storyboard項目者有些一丁點的期待外,同時也是希望有開發者提供更好的處理方式。對比其他IDE,,就算了。追求完美我們是天生的,盡情開發我們也是認真的。
八、源碼下載
地址:https://github.com/minggo620/iOSDesignable.git