幾個UI小技巧,提高效率
減少程式碼量就是減少bug量,這也是程式開發中的真理之一。 這句話引起了我的共鳴,必須留著.
一些IB小技巧
同時新增多個outlet
在IB中,選中一個view並右鍵點選,將會出現灰色的HUD,可以在其上方便地拖拉或設定事件和outlet。你可以同時開啟多個這樣的面板來一次性新增所有outlet。右鍵點選面板,隨便拖動一下面板,然後再開啟另一個。你會發現前一個面板也留下來了,這樣你就可以方便地進行拖拽設定了。
當然,對於成組和行為類似的IBOutlet,應該直接使用IBOutletCollection來進行處理會更方便。
視覺化座標距離
IB最煩人的問題就是對其。用程式碼的時候我們可以明確地指定x,y座標,但是換到IB的時候我們更多的時候是靠拖拽UIView來佈局。比如需要三個間隔相同的label,除了用強大的肉眼來估測距離是否相等以外,難道只能乖乖分別選中三個label,記下它們的座標然後開啟計算器來做加減法麼?
顯然不要那麼笨,試試看選中一個label,然後按住option鍵並將滑鼠移動到其他label上試試?你可以發現view之間的距離都以很容易理解的方式顯示出來了。不僅是同層次的view,被選中view與其他層次的view之間的距離關係也可以同樣顯示。
在一組view層次中進行選擇
對於一些複雜的view層級關係,我們往往直接在IB中選擇會比較困難。比如view相互覆蓋時,我們很難甚至不能在編輯檢視中選中底層的view。這時候一般的做法是開啟左側的view層級面板,一層層展開然後選擇自己需要的view。其實我們也有更簡單的方法:按住Cmd
和Shift
,然後在需要選擇的view上方按右鍵,就可以列出在點選位置上所有的view的列表。藉此就可以方便快速地選中想要的view了。
新增輔助線
這麼高大上的技巧必須放在最後啊…在左邊的層級列表中雙擊某個view,然後Cmd+_
或者Cmd+|
即可在選中的view上新增一條水平或者垂直中心的輔助線。當然這個輔助線是可以隨意移動的。如果幹過設計的同學肯定明白這個的意義了,在之後的對齊和設計變更的時候都有重要的參考價值。
使用xib的 runtime attribute :
在使用的時候,我們在 IB 裡想要適用樣式的 UILabel
新增 runtime attribute 就可以了:
不過不論哪種做法,缺點都是我們無法在 IB 中直觀地看到 label 的變化。當然,可以通過為自定義的 UILabel
@IBDesignable
來克服這個缺點,不過這也需要額外的工作量。還是希望 Xcode 和 IB 能夠進步,原生支援類似的樣式組織方式吧。不過就因此放棄簡單明瞭的 UI 構建方式,未免有些過於武斷。
實踐經驗
利用 @IBInspectable 減少程式碼設定
通過 IB 設定 view 的屬性有一個侷限,那就是有一些屬性沒有暴露在 IB 的設定面板中,或者是設定的時候有可能要“轉個彎”。雖然在 IB 面板中已經包含了八九成經常使用的屬性,但是難免會有「漏網之魚」。我們在工程實踐中最常遇到的情形有兩種:為一個顯示文字的 view 設定本地化字串,以及為一個 image view 設定圓角。
這兩個課題我們都使用在對應的 view 中新增 @IBInspectable
的 extension 方法來解決。比如對於本地化字串的問題,我們會有類似這樣的 extension:
extension UILabel {
@IBInspectable var localizedKey: String? {
set {
guard let newValue = newValue else { return }
text = NSLocalizedString(newValue, comment: "")
}
get { return text }
}
}
extension UIButton {
@IBInspectable var localizedKey: String? {
set {
guard let newValue = newValue else { return }
setTitle(NSLocalizedString(newValue, comment: ""), for: .normal)
}
get { return titleLabel?.text }
}
}
extension UITextField {
@IBInspectable var localizedKey: String? {
set {
guard let newValue = newValue else { return }
placeholder = NSLocalizedString(newValue, comment: "")
}
get { return placeholder }
}
}
這樣,在 IB 中我們就可以利用對應型別的 Localized Key 來直接設定本地化字串了:
設定圓角也類似,為 UIImageView
(或者甚至是 UIView
) 引入這樣的擴充套件,並直接在 IB 中進行設定,可以避免很多模板程式碼:
@IBInspectable var cornerRadius: CGFloat {
get {
return layer.cornerRadius
}
set {
layer.cornerRadius = newValue
layer.masksToBounds = newValue > 0
}
}
@IBInspectable
實際上和上面提到的 UILabel
的 style 方法一樣,它們都使用了 runtime attribute。顯然,你也可以把 UILabel
style 寫成一個 @IBInspectable
,來方便在 IB 中直接設定樣式。
@IBOutlet 的 didSet
雖然這個小技巧並不會對 IB 或者 SB 的使用帶來實質性的改善,但是我覺得還是值得一提。如果我們由於某種原因,確實需要在程式碼中設定一些 view 的屬性,在連線 @IBOutlet
後,不少開發者會選擇在 viewDidLoad
中進行設定。其實個人認為一個更合適的地方是在該 @IBoutlet
的 didSet
中進行。@IBoutlet
所修飾的也是一個屬性,這個關鍵詞所做的僅只是將屬性暴露給 IB,所以它的各種屬性觀察方法 (willSet
,didSet
等) 也會被正常呼叫。比如,下面我們實際專案中的一段程式碼:
@IBOutlet var myTextField: UITextField! {
didSet {
// Workaround for https://openradar.appspot.com/28751703
myTextField.layer.borderWidth = 1.0
myTextField.layer.borderColor = UIColor.lineGreen.cgColor
}
}
這麼做可以讓設定 view 的程式碼和 view 本身相對集中,也可以使 viewDidLoad
更加乾淨。
用storyboard 方法建立的UIViewcontroller ,awakeFromNib 和 - (id)initWithCoder:(NSCoder *)aDecoder是一起出現的,先呼叫- (id)initWithCoder:(NSCoder *)aDecoder 再呼叫 awakeFromNib。
用 - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil來生成UIViewcontroller是不會呼叫上面2個函式的!問什麼呢?這其實是2套初始化方法,第一套使用storyboard 方法建立的UIViewcontroller,確實是從nib檔案中decode出了 viewcontroller;第二套方法,也用到了nib,但是這個nib僅僅包含view的資訊,根本沒有包含viewcontroller物件,viewcontroller物件不是從nib中decode出來的!
下面的圖片也可以說明2個的不同 :
在storyboard中的是一個真正的ViewController , 解檔時獲得全部ViewController
在 xib 中的只有一個view , 解檔時獲得的是ViewController的view .
Content Hugging 和 Content Compression Resistance
在UIView及其子類中 , 這個頁面可以調整寬度高度的優先順序(拉到最下面)。 比如一個頁面,有2個可變的寬度的UILabel,設定這個優先順序,可以是一個顯示完全,另一個被壓縮.
這兩個屬性對有intrinsic content size的控制元件(例如button,label)非常重要。通俗的講,具有intrinsic content size的控制元件自己知道(可以計算)自己的大小,例如一個label,當你設定text,font之後,其大小是可以計算到的。關於intrinsic content size官方的解釋:
Custom views typically have content that they display of which the layout system is unaware. Overriding this method allows a custom view to communicate to the layout system what size it would like to be based on its content. This intrinsic size must be independent of the content frame, because there’s no way to dynamically communicate a changed width to the layout system based on a changed height, for example.
好了,瞭解了intrinsic content size的概念之後,下面就重點討論Content Hugging 和 Content Compression Resistance了。
UIView中關於Content Hugging 和 Content Compression Resistance的方法有:
- (void)setContentCompressionResistancePriority:(UILayoutPriority)priority forAxis:(UILayoutConstraintAxis)axis
- (void)setContentHuggingPriority:(UILayoutPriority)priority forAxis:(UILayoutConstraintAxis)axis
大概的意思就是設定優先順序的。
Hugging priority 確定view有多大的優先順序阻止自己變大。
Compression Resistance priority確定有多大的優先順序阻止自己變小。
從一個普通的View中獲取到載入這個view的ViewController 是個類別. 很多需要些回撥的地方都可以省略了
#import <UIKit/UIKit.h>
@interface UIView (ViewController)
- (UIViewController *)viewController;
@end
#import "UIView+ViewController.h"
@implementation UIView (ViewController)
- (UIViewController *)viewController
{
UIResponder *next = [self nextResponder];
do {
if ([next isKindOfClass:[UIViewController class]]) {
return (UIViewController *)next;
}
next = [next nextResponder];
} while (next != nil);
return nil;
}
@end