Interface Builder一些使用技巧
當年輕的開發者朋友們寫了一大堆的代碼時,他們會覺得很高興,并且喜歡夸耀自己寫的項目有成千上萬行代碼。
隨著經驗越來越豐富,我們意識到代碼是昂貴的,因為要花很多時間去維護、調試和尋找原因。
這也是為什么我們應該使用工具來幫助我們避免寫出樣板代碼。Interface Builder就是這些工具中的一個。
下面說一些我喜歡的小提示和技巧。
Storyboards還是Xib
很顯然,蘋果是極力推薦我們使用storyboard的,因為下列的一些特性只在storyboard中才有:
- Prototype cells
- Segues
- Container View Controllers
很多人抱怨在團隊合作的時候使用storyboard很困難,因為在合并代碼的時候會有沖突。以前這個問題是會經常出現,但是現在storyboard的格式更好、更容易理解了。
即使這樣,我也不喜歡干合并代碼的活。不過幸運的是,當我們使用多個storyboard合理地劃分項目時,就可以避免很多潛在的沖突。
我比較喜歡給每一個場景創建一個storyboard,例如最新的那個app:
- 入職 — 新建賬戶以及初始的信息界面
- 新建流 — 當用戶可以新建內容時
- 活動 — 查看提到自己、自己或其他用戶的活動的相關界面
iOS 9給我們介紹了 Storyboard References ,使我們能夠更容易地使用多個storyboard來分割應用。
如果需要部署在老版本的iOS target上呢?
玩轉Storyboard, Xib或類引用
如果你運氣不夠好,未能使用iOS9作為最低的部署目標,仍舊可以用你自己的方法實現。雖然需要花費點功夫,但這些努力都是值得的。多年以來,我一直在使用一個非常簡單的擴展去擁有我自己的Storyboard, xib 和 類引用。
擴展的主要內容:
- 在storyboard中新建一個空的UIViewController
- 在UIViewController中用IBInspectable擴展來支持自定義的replacementIdentifier圖式
storyboard.StoryboardName.ControllerIdentifier
storyboard.StoryboardName -> loads the initial vc
class.name
xib.name
Objective-C
@interface UIViewController (KZReplacement)@property (nonatomic, copy) IBInspectable NSString *replacementIdentifier;
//! 如果設了replacementIdentifier,就新建一個controller,否則返回self
- (UIViewController *)kz_replacementController;
@end</pre>
@interface UIViewController (KZReplacement) @property (nonatomic, copy) IBInspectable NSString *replacementIdentifier; //! 如果設了replacementIdentifier,就新建一個controller,否則返回self
(UIViewController *)kz_replacementController; @end</pre> </div>
在Interface Builder里是這樣的:
這個方法有一個簡單的實現,下面的是一個最“復雜”的:
Objective-C
- (UIViewController )kz_replacementControllerWithIdentifier:(NSString )identifier; { NSDictionary *replacementSchemes = @{
@"class" : [NSValue valueWithPointer:@selector(kz_createControllerWithClassIdentifier:)], @"storyboard" : [NSValue valueWithPointer:@selector(kz_loadControllerWithStoryboardIdentifier:)], @"xib" : [NSValue valueWithPointer:@selector(kz_loadControllerWithXibIdentifier:)]
};
__block UIViewController replacement = nil; [replacementSchemes enumerateKeysAndObjectsUsingBlock:^(NSString scheme, NSValue selectorValue, BOOL stop) {
const struct _NSRange range = [identifier rangeOfString:scheme]; if (range.location == 0) { *stop = YES; UIViewController *(*objc_msgSendTyped)(id, SEL, NSString *) = (UIViewController *(*)(id, SEL, NSString *))objc_msgSend; replacement = objc_msgSendTyped(self, selectorValue.pointerValue, [identifier substringFromIndex:range.length + 1]); }
}];
return replacement;
}</pre>
- (UIViewController *)kz_replacementControllerWithIdentifier:(NSString *)identifier; { NSDictionary *replacementSchemes = @{ @"class" : [NSValuevalueWithPointer:@selector(kz_createControllerWithClassIdentifier:)], @"storyboard" : [NSValuevalueWithPointer:@selector(kz_loadControllerWithStoryboardIdentifier:)], @"xib" : [NSValuevalueWithPointer:@selector(kz_loadControllerWithXibIdentifier:)] }; __blockUIViewController *replacement = nil; [replacementSchemesenumerateKeysAndObjectsUsingBlock:^(NSString *scheme, NSValue *selectorValue, BOOL *stop) { const struct _NSRangerange = [identifierrangeOfString:scheme]; if (range.location == 0) { *stop = YES; UIViewController *(*objc_msgSendTyped)(id, SEL, NSString *) = (UIViewController *(*)(id, SEL, NSString *))objc_msgSend; replacement = objc_msgSendTyped(self, selectorValue.pointerValue, [identifiersubstringFromIndex:range.length + 1]); } }]; return replacement; }
</div>現在你只需在容器內的每一個VC執行這個方法。舉個例子,如果你的app使用了TabBar,下面的方法對你每一個UITabBarController的子類都會起作用:
Objective-C
- (void)awakeFromNib; { [super awakeFromNib]; [super setViewControllers:[self processViewControllersByReplacingMocks:self.viewControllers] animated:NO]; }
(void)setViewControllers:(NSArray *)viewControllers animated:(BOOL)animated; { [super setViewControllers:[self processViewControllersByReplacingMocks:viewControllers] animated:animated]; }
(void)setViewControllers:(NSArray *)viewControllers; { [super setViewControllers:[self processViewControllersByReplacingMocks:viewControllers]]; }
(NSArray )processViewControllersByReplacingMocks:(NSArray )viewControllersAndMocks; { NSMutableArray array = [NSMutableArray new]; [viewControllersAndMocks enumerateObjectsUsingBlock:^(UIViewController viewController, NSUInteger idx, BOOL *stop) {
UIViewController *replacement = [viewController kz_replacementController]; [array addObject:replacement ?: viewController];
}]; return [array copy]; }</pre>
- (void)awakeFromNib; { [super awakeFromNib]; [supersetViewControllers:[selfprocessViewControllersByReplacingMocks:self.viewControllers]animated:NO]; }
- (void)setViewControllers:(NSArray *)viewControllersanimated:(BOOL)animated; { [supersetViewControllers:[selfprocessViewControllersByReplacingMocks:viewControllers]animated:animated]; }
- (void)setViewControllers:(NSArray *)viewControllers; { [supersetViewControllers:[selfprocessViewControllersByReplacingMocks:viewControllers]]; }
(NSArray )processViewControllersByReplacingMocks:(NSArray )viewControllersAndMocks; { NSMutableArray array = [NSMutableArray new]; [viewControllersAndMocksenumerateObjectsUsingBlock:^(UIViewController viewController, NSUInteger idx, BOOL stop) { UIViewController replacement = [viewControllerkz_replacementController]; [arrayaddObject:replacement ?: viewController]; }]; return [arraycopy]; }</pre> </div>
使用行為(Behaviours)
如果要給你們推薦一種模式的話,毫無疑問我會推薦“組合”(composition),這是個非常棒的模式,并且能自然而然地帶來其他好的方面。
行為是一種組合,只能基于Interface Builder配置。
之前我在 objc.io 的架構指南中寫過。
運行時屬性和@IBInspectable
多年前我們就可以直接在IB內設置一些自定義屬性了,但是還有很多人是不知道的。他們在Interface Builder中新建了一個UIView,然后再用代碼來設置View的一些屬性。
Xcode 6的運行時屬性升級到支持@IBInspectable了,將老且脆弱的運行時屬性:
修改成了一個可編輯的屬性,我們再也不會拼寫錯誤了:
可支持的屬性有下列這些類型:
- Int
- CGFloat
- Double
- String
- Bool
- CGPoint
- CGSize
- CGRect
- UIColor
- UIImage
擴展UIKit
你還可以擴展現有的類型,比如UIKit中的類:
Objective-C
extension UIView { @IBInspectable var kz_borderColor: UIColor? {
get { if let colorRef = layer.borderColor { return UIColor(CGColor: colorRef) } return nil } set { layer.borderColor = newValue?.CGColor }
} }</pre>
extensionUIView { @IBInspectable varkz_borderColor: UIColor? { get { if letcolorRef = layer.borderColor { return UIColor(CGColor: colorRef) } return nil } set { layer.borderColor = newValue?.CGColor } } }
</div>給UIView增加了一個漂亮的邊界顏色編輯器
消除副作用
給NSLayoutConstraint增加一個標示符會咋樣呢?iOS7增加了一個identifier字段作調試用,不過我在另外的一些地方使用了自定義的identifier。
我們的應用在不同的iOS設備的顯示略有一些差異,不幸的是Size Classes未能滿足這個特別的設計。
為了處理這個需求,我創建了一種簡單的DSL語法來描述每一種設備的值:
Objective-C
KZ_SPEC(Create, (@{ @"create.size.captionTextWidth" : kz_spec(260, 250, 234, 220), @"create.size.segmentCollectionView.height" : kz_spec(116, 116, 116, 116).offsetBy(16) }))
KZ_SPEC(Create, (@{ @"create.size.captionTextWidth" : kz_spec(260, 250, 234, 220), @"create.size.segmentCollectionView.height" : kz_spec(116, 116, 116, 116).offsetBy(16) }))
</div>并且擴展了NSLayoutConstraint,使我們能夠為實際的界面綁定規格。這樣就避免了在view中寫樣板代碼了:
Objective-C
@interface NSLayoutConstraint(KZDesignSpec) @property (nonatomic, copy) IBInspectable NSString* kz_specName; @end
@implementation NSLayoutConstraint (KZDesignSpe) static void const *kDesignSpecKey = &kDesignSpecKey;
-
(NSString *)kz specName; { return objc getAssociatedObject(self, kDesignSpecKey); }
-
(void)setKz specName:(NSString *)kz specName; { objc setAssociatedObject(self, kDesignSpecKey, kz specName, OBJC ASSOCIATION COPY NONATOMIC); self.constant = kz floatForSpec(kz specName); self.identifier = kz specName; } @end
@interface NSLayoutConstraint(KZDesignSpec) @property (nonatomic, copy) IBInspectable NSString kz_specName; @end @implementation NSLayoutConstraint (KZDesignSpe) static void const kDesignSpecKey = &kDesignSpecKey;
- (NSString *)kz_specName; { return objc_getAssociatedObject(self, kDesignSpecKey); }
(void)setKz_specName:(NSString *)kz_specName; { objc_setAssociatedObject(self, kDesignSpecKey, kz_specName, OBJC_ASSOCIATION_COPY_NONATOMIC); self.constant = kz_floatForSpec(kz_specName); self.identifier = kz_specName; } @end</pre> </div>
快捷鍵和隱藏選項
確認你知道自己的快捷鍵以及Interface Builder的一些隱藏選項。
我還記得在我使用了2年的IB后看見一個人使用Media Tab拉出一張圖片,并創建了一個合適尺寸的UIImageView.而這時的我還是手動的……
快捷鍵有很多, 這里給出了一些不錯的 。
當你在設置AutoLayout的時候,記得在拉約束的同時按Option鍵,或者使用編輯菜單。
總結
Interface Builder離完美還有很長的路,但它也遠不是一些開發者認為的一個糟糕的工具。
很多應用不能說是“用Interface Builder來實現太復雜”了,這是很多人在我問他們為什么不用IB的時候的回答。
實際上,很多情況下使用IB足矣,特別是加上我之前給出的擴展,或者是你自己實現的擴展。
其他情況下使用代碼。代碼或IB,不是一個只能二選一的問題。
本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!相關經驗