iOS 常用佈局方式之Constraint

QiShare發表於2019-06-03

級別: ★★☆☆☆
標籤:「iOS AutoLayout」「iOS 自動佈局」「NSLayoutConstraint」
作者: Xs·H
審校: QiShare團隊

沐靈洛 線下分享iOS UIButton根據內容自動佈局時,有和前端同學討論到iOS的常用佈局方式。討論過程十分熱鬧,不容易記錄,但作者認為討論結果有必要記錄一下,希望能幫助到一些同學。 作者將iOS常用佈局方式歸納為Frame、Autoresizing、Constraint、StackView和Masonry五種,並將逐一介紹。 本篇文章介紹Constraint。

Constraint相較於Autoresizing要更加靈活和強大,可以說是一種替代方案。Constraint的全稱是NSLayoutConstraint,也常被稱作AutoLayout,配合著Storyboard可以非常方便地構建頁面。比如作者在上篇文章中沒有實現的同級檢視之間約束問題,使用NSLayoutConstraint將迎刃而解,並且不需要編寫程式碼。在Storyboard中構建的約束關係如下。

iOS 常用佈局方式之Constraint

當然,開發者也可以使用程式碼的形式利用NSLayoutConstraint佈局檢視。比如,作者在4等分檢視的基礎上,在淺灰色contentView上新增一個藏青色(cyanColor)的subView5,使其始終以固定的寬高居中顯示,也就是實現下圖中的效果。

iOS 常用佈局方式之Constraint

實現上述效果的程式碼如下。

- (void)viewDidLoad {
    
    [super viewDidLoad];
    
    UIView *subView5 = [[UIView alloc] initWithFrame:CGRectZero];
    subView5.backgroundColor = [[UIColor cyanColor] colorWithAlphaComponent:.6];
    subView5.translatesAutoresizingMaskIntoConstraints = NO;
    [_contentView addSubview:subView5];
    
    NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:subView5 attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:100.0];
    NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:subView5 attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:200.0];
    NSLayoutConstraint *centerXConstraint = [NSLayoutConstraint constraintWithItem:subView5 attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:_contentView attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:.0];
    NSLayoutConstraint *centerYConstraint = [NSLayoutConstraint constraintWithItem:subView5 attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:_contentView attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:.0];
    
    [_contentView addConstraints:@[widthConstraint, heightConstraint, centerXConstraint, centerYConstraint]];
}
複製程式碼

通過上述程式碼,看一下NSLayoutConstraint的用法。 首先,在使用程式碼利用NSLayoutConstraint佈局檢視時,要先指明該檢視不被Autoresizing所控制(程式碼如下)。否則,會出現約束衝突的情況。

subView5.translatesAutoresizingMaskIntoConstraints = NO;
複製程式碼

然後,約束檢視是通過“設定約束”和“新增約束”兩個步驟來完成的。

設定約束

NSLayoutConstraint有標準的API來設定約束,如下。

/* Create constraints explicitly.  Constraints are of the form "view1.attr1 = view2.attr2 * multiplier + constant" 
 If your equation does not have a second view and attribute, use nil and NSLayoutAttributeNotAnAttribute.
 */
+ (instancetype)constraintWithItem:(id)view1 attribute:(NSLayoutAttribute)attr1 relatedBy:(NSLayoutRelation)relation toItem:(nullable id)view2 attribute:(NSLayoutAttribute)attr2 multiplier:(CGFloat)multiplier constant:(CGFloat)c;
複製程式碼

通俗地解釋一下上面的API:對view1的attr1屬性和view2的attr2屬性以relation這種關係和multiplier這種倍數進行c數值的約束。 比如,約束view1和view2等寬可以這樣寫:

[NSLayoutConstraint constraintWithItem:view1 attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:view2 attribute:NSLayoutAttributeWidth multiplier:1.0 constant:.0];
複製程式碼

其中,attr1和attr2都是從NSLayoutAttribute列舉中取值。

typedef NS_ENUM(NSInteger, NSLayoutAttribute) {
    NSLayoutAttributeLeft = 1,
    NSLayoutAttributeRight,
    NSLayoutAttributeTop,
    NSLayoutAttributeBottom,
    NSLayoutAttributeLeading,
    NSLayoutAttributeTrailing,
    NSLayoutAttributeWidth,
    NSLayoutAttributeHeight,
    NSLayoutAttributeCenterX,
    NSLayoutAttributeCenterY,
    NSLayoutAttributeLastBaseline,
    NSLayoutAttributeBaseline NS_SWIFT_UNAVAILABLE("Use 'lastBaseline' instead") = NSLayoutAttributeLastBaseline,
    NSLayoutAttributeFirstBaseline NS_ENUM_AVAILABLE_IOS(8_0),
    
    NSLayoutAttributeLeftMargin NS_ENUM_AVAILABLE_IOS(8_0),
    NSLayoutAttributeRightMargin NS_ENUM_AVAILABLE_IOS(8_0),
    NSLayoutAttributeTopMargin NS_ENUM_AVAILABLE_IOS(8_0),
    NSLayoutAttributeBottomMargin NS_ENUM_AVAILABLE_IOS(8_0),
    NSLayoutAttributeLeadingMargin NS_ENUM_AVAILABLE_IOS(8_0),
    NSLayoutAttributeTrailingMargin NS_ENUM_AVAILABLE_IOS(8_0),
    NSLayoutAttributeCenterXWithinMargins NS_ENUM_AVAILABLE_IOS(8_0),
    NSLayoutAttributeCenterYWithinMargins NS_ENUM_AVAILABLE_IOS(8_0),
    
    NSLayoutAttributeNotAnAttribute = 0
};
複製程式碼

relation是從NSLayoutRelation列舉中取值。

typedef NS_ENUM(NSInteger, NSLayoutRelation) {
    NSLayoutRelationLessThanOrEqual = -1,
    NSLayoutRelationEqual = 0,
    NSLayoutRelationGreaterThanOrEqual = 1,
};
複製程式碼

綜上,在理解API各引數含義的基礎上將會更容易讀懂上述subView5的4個約束。

新增約束

先看一下例子中新增約束的程式碼,如下。

[_contentView addConstraints:@[widthConstraint, heightConstraint, centerXConstraint, centerYConstraint]];
複製程式碼

作者將4個約束都新增到了contentView上面,當然,執行效果證明這樣新增沒有問題。但有的同學問道:“ 因為subView5的位置被約束到了contentView上,所以centerXConstraint和centerYConstraint被新增到contentView上是容易被理解的,但是widthConstraint和heightConstraint只和subView5有關係,為什麼也新增到了contentView上,不是應該新增到subView5上嗎?”。是的,按照這種說法,widthConstraint和heightConstraint新增到subView5的確更容易理解,於是作者做了如下修改。

[subView5 addConstraints:@[widthConstraint, heightConstraint]];
[_contentView addConstraints:@[centerXConstraint, centerYConstraint]];
複製程式碼

從修改後的執行效果看也是沒有問題的。之所以將4個constraint都新增到了contentView上,是因為**不管是對哪個檢視的約束,只要新增到該檢視或者該檢視的父檢視以及更高層級的父檢視上,都是沒有問題的。**所以,在程式設計中,開發者常常會將多個約束統一新增到某個比較靠近使用者的父檢視上。

關於本篇文章的具體實現細節可以在QiLayoutDemo中檢視。


小編微信:可加並拉入《QiShare技術交流群》。

iOS 常用佈局方式之Constraint

關注我們的途徑有:
QiShare(簡書)
QiShare(掘金)
QiShare(知乎)
QiShare(GitHub)
QiShare(CocoaChina)
QiShare(StackOverflow)
QiShare(微信公眾號)

推薦文章:
iOS UIButton根據內容自動佈局
iOS 指定初始化方法
UIView中的hitTest方法
奇舞週刊

相關文章