iOS 常用布局方式之Constraint

級別: ★☆☆☆☆
標簽:「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中構建的約束關系如下。

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

實現上述效果的代碼如下。

- (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中查看。


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

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

推薦閱讀更多精彩內容