本來以為 Auto Layout 可以用類似 @"|-[view1(viewWidth)]-space-[view2(viewWidth)]-space-[view3(viewWidth)]-|"
這樣的語法自動把三個等寬的 view 等距排列的,沒想到沒辦法。要達到這個效果的話,一個做法是把每個 space 真的塞一個空白的 view 進去,調整這個 view 的寬度,就像 @"|-[view1(viewWidth)]-[space1]-[view2(viewWidth)]-[space2(==space1)]-[view3(viewWidth)]-|"
,但這麼做會生出一堆空白 view,怎麼想都很蠢…另外一條路就是針對每個 view 單獨設定 constraint。
constraint 的建構式長這樣:
+ (instancetype)constraintWithItem:(id)view1
attribute:(NSLayoutAttribute)attr1
relatedBy:(NSLayoutRelation)relation
toItem:(id)view2
attribute:(NSLayoutAttribute)attr2
multiplier:(CGFloat)multiplier
constant:(CGFloat)c
如果我們這樣設定:
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:superView
attribute:NSLayoutAttributeCenterX
multiplier:multiplier
constant:constant];
就會有如下的效果:
$$view1.centerX = multiplier \times superView.centerX + constant$$
因此我們希望最終得到的公式形式為:
$$view_k.centerX = a_k \times superView.centerX + b_k$$
那麼只要知道 \(a_k\) \(b_k\) 的公式,就可以用來設定每個 view 的 constraint。
透過觀察,可以發現幾件事,首先,\(view_0.centerX\) 的位置就是它寬度的一半:
$$\begin{aligned} view_0.centerX &= \frac{1}{2} \cdot view.width \ &= 0 \cdot superView.centerX + \frac{1}{2} \cdot view.width \end{aligned}$$
另外,\(superView\) 的寬度等於 \(superView.centerX\) 的兩倍,也等於 n 倍的 \(view.width\) 加上 (n-1) 倍的間隔 (\(space\)),因此我們可以把 \(space\) 表示如下:
$$\begin{aligned} superView.width &= superView.centerX \times 2 = n \times view.width + (n-1) \times space \ \Rightarrow space &= \frac{2\cdot{superView.centerX}-n\cdot{view.width}}{n-1} \end{aligned}$$
最後,\(view_k.centerX\) 等於 \(view_0.centerX\) 加上 \(k\) 倍的 \(view.width\) 加 \(k\) 倍的 \(space\):
$$\begin{aligned} view_k.centerX &= view_0.centerX + k \times (view.width + space) \ &= \frac {1}{2} \cdot view.width + k \cdot view.width + k \cdot (\frac{2\cdot{superView.centerX}-n\cdot{view.width}}{n-1}) \ &= \frac {2 \cdot k}{n-1} \times superView.centerX + (\frac {1}{2} + k - \frac{n }{n-1} \cdot k) \times view.width \ &= \frac {2 \cdot k}{n-1} \times superView.centerX + \frac {1} {2} \times (1-\frac {2 \cdot k}{n-1}) \times view.width \end{aligned}$$
這時假設
$$a_k = \frac {2}{n-1} \cdot k$$
就可以得到
$$\begin{aligned} \Rightarrow view_k.centerX &= \frac {2 \cdot k}{n-1} \times superView.centerX + \frac {1} {2} \times (1-\frac {2 \cdot k}{n-1}) \times view.width \ &= a_k \times superView.centerX + \frac {1} {2} (1-a_k) \times view.width \end{aligned}$$
也就是
$$\begin{aligned} multiplier_k &= \frac {2}{n-1} \cdot k \ constant_k &= (1-multiplier_k) \times \frac {1} {2} view.width \end{aligned}$$
寫成 code 可以用這樣的做法:
NSDictionary *views = NSDictionaryOfVariableBindings(view1, view2, view3, view4, view5, view6);
NSArray *subViews = @[view1, view2, view3, view4, view5, view6];
CGFloat viewWidth = 30.0;
NSDictionary *metrics = @{@"viewWidth": @30};
CGFloat multiplier = 2.0 / (subViews.count - 1.0);
CGFloat constantFactor = viewWidth / 2.0;
[superView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[view1(viewWidth)]->=0-[view2(viewWidth)]->=0-[view3(viewWidth)]->=0-[view4(viewWidth)]->=0-[view5(viewWidth)]->=0-[view6(viewWidth)]|"
options:NSLayoutFormatAlignAllTop|NSLayoutFormatAlignAllBottom
metrics:metrics
views:views]];
[superView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[view1(viewWidth)]|"
options:0
metrics:metrics
views:views]];
for (int k=0; k<subViews.count; k++) {
[superView addConstraint:[NSLayoutConstraint constraintWithItem:subViews[k]
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:superView
attribute:NSLayoutAttributeCenterX
multiplier:k * multiplier
constant:(1.0 - multiplier * k) * constantFactor]];
}
但是這種做法會讓內部的 subView 貼緊 superView,呈現 @"|[view1]-[view2]-...-[viewk]|"
的形式。如果希望第一個 subView 和最後一個 subView 與 superView 之間有邊界,呈現 @"|-[view1]-[view2]-...-[viewk]-|"
的形式,用一樣的方式推導,可以算出
$$\begin{aligned} multiplier_k &= \frac {2}{n-1} \cdot k \ constant_k &= (1-multiplier_k) \times (\frac {1} {2} view.width + margin) \end{aligned}$$
code 則改為:
NSDictionary *views = NSDictionaryOfVariableBindings(view1, view2, view3, view4, view5, view6);
NSArray *subViews = @[view1, view2, view3, view4, view5, view6];
CGFloat viewWidth = 30.0;
CGFloat margin = 8.0;
NSDictionary *metrics = @{@"viewWidth": @30, @"margin":@8};
CGFloat multiplier = 2.0 / (subViews.count - 1.0);
CGFloat constantFactor = viewWidth / 2.0 + margin;
[superView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-margin-[view1(viewWidth)]->=0-[view2(viewWidth)]->=0-[view3(viewWidth)]->=0-[view4(viewWidth)]->=0-[view5(viewWidth)]->=0-[view6(viewWidth)]-margin-|"
options:NSLayoutFormatAlignAllTop|NSLayoutFormatAlignAllBottom
metrics:metrics
views:views]];
[superView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[view1(viewWidth)]|"
options:0
metrics:metrics
views:views]];
for (int k=0; k<subViews.count; k++) {
[superView addConstraint:[NSLayoutConstraint constraintWithItem:subViews[k]
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:superView
attribute:NSLayoutAttributeCenterX
multiplier:k * multiplier
constant:(1.0 - multiplier * k) * constantFactor]];
}