細(xì)數(shù)AutoLayout以來UIView和UIViewController新增的相關(guān)API
2014-10-26 12:29
編輯:
分類:iOS開發(fā)
來源:chun.tips
2
45127
UIViewAutolayoutUIViewController
細(xì)數(shù)AutoLayout以來UIView和UIViewController新增的相關(guān)API – UIViewController篇
UILayoutSupport
<pre style="margin: 1.5em 0px; padding: 0px; font-size: 1em; width: 712.797px; overflow: auto; background: rgb(247, 247, 247); font-style: normal; font-variant: normal; font-weight: normal; font-stretch: normal; line-height: 1.5em; font-family: "Andale Mono", "Lucida Console", monospace; white-space: pre;">
@property(nonatomic,readonly,retain) id<uilayoutsupport> topLayoutGuide NS_AVAILABLE_IOS(7_0); </uilayoutsupport>
@property(nonatomic,readonly,retain) id<uilayoutsupport> bottomLayoutGuide NS_AVAILABLE_IOS(7_0); </uilayoutsupport>
@protocol UILayoutSupport
@property(nonatomic,readonly) CGFloat length;
@end
</pre>
從iOS 7以來,當(dāng)我們的視圖控制器結(jié)構(gòu)中有NavigationBar瘾境,TabBar或者ToolBar的時候污尉,它們的translucent屬性的默認(rèn)值改為了YES簸喂,并且當(dāng)前的ViewController的高度會是整個屏幕的高度。(比如一個場景:拖動TableView的時候弃理,上面的NavigationBar能夠透過去看到TableView的內(nèi)容兽赁。)
為了確保我們的視圖不被這些Bar覆蓋,我們可以在我們AutoLayout布局中使用topLayoutGuide和bottomLayoutGuide這兩個屬性封寞。像這樣:
<pre style="margin: 1.5em 0px; padding: 0px; font-size: 1em; width: 712.797px; overflow: auto; background: rgb(247, 247, 247); font-style: normal; font-variant: normal; font-weight: normal; font-stretch: normal; line-height: 1.5em; font-family: "Andale Mono", "Lucida Console", monospace; white-space: pre;">
-
NSDictionary *views = @{
"topLayoutGuide"
: self.topLayoutGuide, @
"myLabel"
: myLabel};
-
[NSLayoutConstraint constraintsWithVisualFormat:@
"V:[topLayoutGuide]-[myView]"
options:0 metrics:nil views:views]
</pre>
這個時候我們的視圖就不會被Bar所覆蓋,顯示在了Bar下方:
[圖片上傳失敗...(image-e8d953-1513687006841)]
并且使用這個屬性布局時仅财,在traitCollection改變時(旋轉(zhuǎn)屏幕)狈究,它的值也會動態(tài)變化。上述代碼满着,在橫屏情況下谦炒,navigationbar高度變了之后贯莺,仍然能夠正確顯示风喇。
這兩個guides的計算方式如下:
topLayoutGuide
是通過計算 View Controller->View->Top 到 覆蓋這個View最下層的那個Bar(像Navigation Bar) -> Bottom 的距離
bottomLayoutGuide
是通過計算 View Controller->View->Bottom 到 覆蓋這個View上層那個Bar(像Tab bar) -> Top 的距離
如果我們不使用AutoLayout布局,我們也可以通過Guide的length屬性獲得相應(yīng)的距離缕探。我們應(yīng)該在-viewDidLayoutSubviews或者-layoutSubviews調(diào)用super之后魂莫,再去獲得length這個值,以確保正確爹耗。
UIConstraintBasedLayoutCoreMethods
<pre style="margin: 1.5em 0px; padding: 0px; font-size: 1em; width: 712.797px; overflow: auto; background: rgb(247, 247, 247); font-style: normal; font-variant: normal; font-weight: normal; font-stretch: normal; line-height: 1.5em; font-family: "Andale Mono", "Lucida Console", monospace; white-space: pre;">
-
- (
void
)updateViewConstraints NS_AVAILABLE_IOS(6_0);
</pre>
UIViewController中也新增了一個更新布局約束的方法耙考,在AutoLayout UIView相關(guān)API的筆記中,詳細(xì)講述了UIView的一組更新布局約束的方法。
這個方法默認(rèn)的實(shí)現(xiàn)是調(diào)用對應(yīng)View的 -updateConstraints 潭兽。ViewController的View在更新視圖布局時倦始,會先調(diào)用ViewController的updateViewConstraints 方法。我們可以通過重寫這個方法去更新當(dāng)前View的內(nèi)部布局山卦,而不用再繼承這個View去重寫-updateConstraints方法鞋邑。我們在重寫這個方法時,務(wù)必要調(diào)用 super 或者 調(diào)用當(dāng)前View的 -updateConstraints 方法账蓉。
UITraitEnvironment
又一次看到了UITraitEnvironment協(xié)議枚碗,在UIKit Framework中,有四個類支持這個協(xié)議铸本,分別是UIScreen, UIViewController肮雨,UIView 和 UIPresentationController。所以當(dāng)視圖的traitCollection改變時箱玷,UIViewController能夠捕獲到這個消息怨规,并做對應(yīng)處理的陌宿。 更多解釋可以參考上一篇文章詳解UICoordinateSpace和UIScreen在iOS 8上的坐標(biāo)問題。
關(guān)于Size Class和UITraitCollection的概念可參考如下鏈接:
WWDC 2014 Session筆記 - iOS界面開發(fā)的大一統(tǒng)
From: onecat’s Blog
From: Joywii’s Blog
另外波丰,UIViewController還另外提供了以下兩個方法:
<pre style="margin: 1.5em 0px; padding: 0px; font-size: 1em; width: 712.797px; overflow: auto; background: rgb(247, 247, 247); font-style: normal; font-variant: normal; font-weight: normal; font-stretch: normal; line-height: 1.5em; font-family: "Andale Mono", "Lucida Console", monospace; white-space: pre;">
-
- (
void
)setOverrideTraitCollection:(UITraitCollection *)collection forChildViewController:(UIViewController *)childViewController NS_AVAILABLE_IOS(8_0);
- (UITraitCollection *)overrideTraitCollectionForChildViewController:(UIViewController *)childViewController NS_AVAILABLE_IOS(8_0);
</pre>
我們可以通過調(diào)用ViewController的setOverrideTraitCollection方法為它的ChildViewController重新設(shè)置traitCollection的值限番。一般情況下traitCollection值從父controller傳到子controller是不做修改的。當(dāng)我們自己實(shí)現(xiàn)一個容器Controller的時候呀舔,我們可以使用這個方法進(jìn)行調(diào)整弥虐。
相對的,我們可以通過overrideTraitCollectionForChildViewController方法獲得ChildViewController的traitCollection值媚赖。
UIContentContainer
iOS 8上隨著Size Class概念的提出霜瘪,UIViewController支持了UIContentContainer這樣一組新的協(xié)議:
<pre style="margin: 1.5em 0px; padding: 0px; font-size: 1em; width: 712.797px; overflow: auto; background: rgb(247, 247, 247); font-style: normal; font-variant: normal; font-weight: normal; font-stretch: normal; line-height: 1.5em; font-family: "Andale Mono", "Lucida Console", monospace; white-space: pre;">
-
- (
void
)systemLayoutFittingSizeDidChangeForChildContentContainer:(id <uicontentcontainer>)container NS_AVAILABLE_IOS(8_0); </uicontentcontainer>
- (CGSize)sizeForChildContentContainer:(id <uicontentcontainer>)container withParentContainerSize:(CGSize)parentSize NS_AVAILABLE_IOS(8_0); </uicontentcontainer>
-
- (
void
)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id <uiviewcontrollertransitioncoordinator>)coordinator NS_AVAILABLE_IOS(8_0); </uiviewcontrollertransitioncoordinator>
-
- (
void
)willTransitionToTraitCollection:(UITraitCollection *)newCollection withTransitionCoordinator:(id <uiviewcontrollertransitioncoordinator>)coordinator NS_AVAILABLE_IOS(8_0); </uiviewcontrollertransitioncoordinator>
</pre>
UIViewController對這組協(xié)議提供了默認(rèn)的實(shí)現(xiàn)。我們自定義ViewController的時候可以重寫這些方法來調(diào)整視圖布局惧磺,比如我們可以在這些方法里調(diào)整ChildViewControler的位置颖对。當(dāng)我們重寫這些協(xié)議方法時,我們通常都去調(diào)用 super磨隘。
viewWillTransitionToSize: ViewController的View的size被他的Parent Controller改變時缤底,會觸發(fā)這個方法。(比如rootViewController在它的window旋轉(zhuǎn)的時候)番捂。我們在重寫這個方法時个唧,確保要調(diào)用super,來保證size改變的這條消息能夠正常傳遞給它的Views或者ChildViewControllers。
willTransitionToTraitCollection: 當(dāng)ViewController的traitCollection的值將要改變時會調(diào)用這個方法设预。這個方法是在 UITraitEnvironment協(xié)議方法 traitCollectionDidChange:之前被調(diào)用徙歼。我們在重寫這個方法時,也要確保要調(diào)用super來保證消息的傳遞鳖枕。比如魄梯,我們可以像這樣在traitCollection值改變時,對視圖做對應(yīng)的動畫進(jìn)行調(diào)整:
<pre style="margin: 1.5em 0px; padding: 0px; font-size: 1em; width: 712.797px; overflow: auto; background: rgb(247, 247, 247); font-style: normal; font-variant: normal; font-weight: normal; font-stretch: normal; line-height: 1.5em; font-family: "Andale Mono", "Lucida Console", monospace; white-space: pre;">
-
- (
void
)willTransitionToTraitCollection:(UITraitCollection *)newCollection
withTransitionCoordinator:(id <uiviewcontrollertransitioncoordinator>)coordinator </uiviewcontrollertransitioncoordinator>
{
-
[
super
willTransitionToTraitCollection:newCollection
withTransitionCoordinator:coordinator];
[coordinator animateAlongsideTransition:^(id <uiviewcontrollertransitioncoordinatorcontext> context) { </uiviewcontrollertransitioncoordinatorcontext>
-
if
(newCollection.verticalSizeClass == UIUserInterfaceSizeClassCompact) {
-
}
else
{
}
[self.view setNeedsLayout];
} completion:nil];
}
</pre>
sizeForChildContentContainer:一個容器ViewController可以使用這個方法設(shè)置ChildViewController的size宾符。當(dāng)容器ViewControllerviewWillTransitionToSize:withTransitionCoordinator:被調(diào)用時(我們重寫這個方法時要調(diào)用Super)酿秸,sizeForChildContentContainer方法將會被調(diào)用。然后我們可以把需要設(shè)置的size發(fā)送給ChildViewController魏烫。當(dāng)我們設(shè)置的這個size和當(dāng)前ChildViewController的size一樣辣苏,那么ChildViewController的viewWillTransitionToSize方法將不會被調(diào)用。
sizeForChildContentContainer默認(rèn)的實(shí)現(xiàn)是返回 parentSize则奥。
systemLayoutFittingSizeDidChangeForChildContentContainer:當(dāng)滿足如下情況考润,這個方法會被調(diào)用:
當(dāng)前ViewController沒有使用AutoLayout布局
ChildrenViewController的View使用了AutoLayout布局
ChildrenViewController View -systemLayoutSizeFittingSize:方法返回的值改變(View由于內(nèi)容的變化,size也出現(xiàn)了變化)
preferredContentSize
<pre style="margin: 1.5em 0px; padding: 0px; font-size: 1em; width: 712.797px; overflow: auto; background: rgb(247, 247, 247); font-style: normal; font-variant: normal; font-weight: normal; font-stretch: normal; line-height: 1.5em; font-family: "Andale Mono", "Lucida Console", monospace; white-space: pre;">
// From UIContentContainer Protocol
@property (nonatomic, readonly) CGSize preferredContentSize NS_AVAILABLE_IOS(8_0);
-
- (
void
)preferredContentSizeDidChangeForChildContentContainer:(id <uicontentcontainer>)container NS_AVAILABLE_IOS(8_0); </uicontentcontainer>
// From UIViewController
@property (nonatomic) CGSize preferredContentSize NS_AVAILABLE_IOS(7_0);
</pre>
preferredContentSize在UIContentContainer協(xié)議中是只讀的读处,對應(yīng)的UIViewController有可寫的版本糊治。我們可以使用preferredContentSize來設(shè)置我們期望的ChildViewController的界面大小。舉個例子罚舱,如果應(yīng)用中使用的popOver大小會發(fā)生變化井辜,iOS7之前我們可以用contentSizeForViewInPopover來調(diào)整绎谦。iOS7開始這個API被廢棄,我們可以使用preferredContentSize來設(shè)置粥脚。
當(dāng)一個容器ViewController的ChildViewController的這個值改變時窃肠,UIKit會調(diào)用preferredContentSizeDidChangeForChildContentContainer這個方法告訴當(dāng)前容器ViewController。我們可以在這個方法里根據(jù)新的Size對界面進(jìn)行調(diào)整刷允。
總結(jié) UIViewController到目前為止(iOS 8.1), 關(guān)于布局的API最大的變化是iOS8中新增支持的兩組協(xié)議:UITraitEnvironment 和 UIContentContainer冤留。我們可以在學(xué)習(xí)中通過Demo實(shí)現(xiàn)這些協(xié)議,來觀察ViewController中這些方法最終被調(diào)用的時機(jī)树灶。
細(xì)數(shù)AutoLayout以來UIView和UIViewController新增的相關(guān)API--UIView篇
iOS8上關(guān)于UIView的Margin新增了3個APIs:
<pre style="margin: 1.5em 0px; padding: 0px; font-size: 1em; width: 712.797px; overflow: auto; background: rgb(247, 247, 247); font-style: normal; font-variant: normal; font-weight: normal; font-stretch: normal; line-height: 1.5em; font-family: "Andale Mono", "Lucida Console", monospace; white-space: pre;">
@property (nonatomic) UIEdgeInsets layoutMargins NS_AVAILABLE_IOS(8_0);
@property (nonatomic) BOOL preservesSuperviewLayoutMargins NS_AVAILABLE_IOS(8_0);
-
- (
void
)layoutMarginsDidChange NS_AVAILABLE_IOS(8_0);
</pre>
在iOS 8中纤怒,可以使用layoutMargins去定義view之間的間距,該屬性只對AutoLayout布局生效。
因此AutoLayout中NSLayoutAttribute的枚舉值有了相應(yīng)的更新:
<pre style="margin: 1.5em 0px; padding: 0px; font-size: 1em; width: 712.797px; overflow: auto; background: rgb(247, 247, 247); font-style: normal; font-variant: normal; font-weight: normal; font-stretch: normal; line-height: 1.5em; font-family: "Andale Mono", "Lucida Console", monospace; white-space: pre;">
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),
</pre>
通過在Xcode中測試打印天通,發(fā)現(xiàn)UIView默認(rèn)的layoutMargins的值為 {8, 8, 8, 8}泊窘,我們可以通過修改這個值來改變View之間的距離。
在我們改變View的layoutMargins這個屬性時像寒,會觸發(fā)- (void)layoutMarginsDidChange這個方法烘豹。我們在自己的View里面可以重寫這個方法來捕獲layoutMargins的變化。在大多數(shù)情況下诺祸,我們可以在這個方法里觸發(fā)drawing和layout的Update携悯。
preservesSuperviewLayoutMargins這個屬性默認(rèn)是NO。如果把它設(shè)為YES序臂,layoutMargins會根據(jù)屏幕中相關(guān)View的布局而改變蚌卤。舉個例子:
[圖片上傳失敗...(image-523070-1513687006841)]
如上圖,有三個View奥秆,其中藍(lán)色View的layoutMargins設(shè)為 UIEdgeInsetsMake(50, 50, 50, 50),黃色View的layoutMargins設(shè)為 UIEdgeInsetsMake(10, 10, 10, 10)咸灿。對黃色View的布局約束代碼如下:
<pre style="margin: 1.5em 0px; padding: 0px; font-size: 1em; width: 712.797px; overflow: auto; background: rgb(247, 247, 247); font-style: normal; font-variant: normal; font-weight: normal; font-stretch: normal; line-height: 1.5em; font-family: "Andale Mono", "Lucida Console", monospace; white-space: pre;">
[NSLayoutConstraint constraintWithItem:yellowView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:blueView attribute:NSLayoutAttributeWidth multiplier:1.0 constant:0.0];
[NSLayoutConstraint constraintWithItem:yellowView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:blueView attribute:NSLayoutAttributeHeight multiplier:0.5 constant:0.0];
[NSLayoutConstraint constraintWithItem:yellowView attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:blueView attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0];
[NSLayoutConstraint constraintWithItem:yellowView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:blueView attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0];
</pre>
對黑色View的布局代碼如下:
<pre style="margin: 1.5em 0px; padding: 0px; font-size: 1em; width: 712.797px; overflow: auto; background: rgb(247, 247, 247); font-style: normal; font-variant: normal; font-weight: normal; font-stretch: normal; line-height: 1.5em; font-family: "Andale Mono", "Lucida Console", monospace; white-space: pre;">
[NSLayoutConstraint constraintWithItem:blackView attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:yellowView attribute:NSLayoutAttributeTrailingMargin multiplier:1.0 constant:0.0];
[NSLayoutConstraint constraintWithItem:blackView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:yellowView attribute:NSLayoutAttributeLeadingMargin multiplier:1.0 constant:0.0];
[NSLayoutConstraint constraintWithItem:blackView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:yellowView attribute:NSLayoutAttributeTopMargin multiplier:1.0 constant:0.0];
[NSLayoutConstraint constraintWithItem:blackView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:yellowView attribute:NSLayoutAttributeBottomMargin multiplier:1.0 constant:0.0];
</pre>
在preservesSuperviewLayoutMargins默認(rèn)為NO的情況下构订,顯示效果就和上圖一樣(間距為10)。當(dāng)設(shè)置黃色View的preservesSuperviewLayoutMargins為YES時避矢,將會獲得如下效果(間距為50):
[圖片上傳失敗...(image-1f985a-1513687006841)]
UIConstraintBasedLayoutInstallingConstraints
<pre style="margin: 1.5em 0px; padding: 0px; font-size: 1em; width: 712.797px; overflow: auto; background: rgb(247, 247, 247); font-style: normal; font-variant: normal; font-weight: normal; font-stretch: normal; line-height: 1.5em; font-family: "Andale Mono", "Lucida Console", monospace; white-space: pre;">
-
@
interface
UIView (UIConstraintBasedLayoutInstallingConstraints)
- (NSArray *)constraints NS_AVAILABLE_IOS(6_0);
-
- (
void
)addConstraint:(NSLayoutConstraint *)constraint NS_AVAILABLE_IOS(6_0);
-
- (
void
)addConstraints:(NSArray *)constraints NS_AVAILABLE_IOS(6_0);
-
- (
void
)removeConstraint:(NSLayoutConstraint *)constraint NS_AVAILABLE_IOS(6_0);
-
- (
void
)removeConstraints:(NSArray *)constraints NS_AVAILABLE_IOS(6_0);
@end
</pre>
以上這五個API中悼瘾,第一個是返回當(dāng)前View中所有的constraints。后面四個方法即將被廢棄审胸,應(yīng)該使用NSLayoutConstraint類中activateConstraint相關(guān)方法替代亥宿。
UIConstraintBasedLayoutCoreMethods
<pre style="margin: 1.5em 0px; padding: 0px; font-size: 1em; width: 712.797px; overflow: auto; background: rgb(247, 247, 247); font-style: normal; font-variant: normal; font-weight: normal; font-stretch: normal; line-height: 1.5em; font-family: "Andale Mono", "Lucida Console", monospace; white-space: pre;">
-
@
interface
UIView (UIConstraintBasedLayoutCoreMethods)
-
- (
void
)updateConstraintsIfNeeded NS_AVAILABLE_IOS(6_0);
-
- (
void
)updateConstraints NS_AVAILABLE_IOS(6_0);
- (BOOL)needsUpdateConstraints NS_AVAILABLE_IOS(6_0);
-
- (
void
)setNeedsUpdateConstraints NS_AVAILABLE_IOS(6_0);
@end
</pre>
setNeedsUpdateConstraints : 當(dāng)一個自定義的View某一個屬性的改變可能影響到界面布局,我們應(yīng)該調(diào)用這個方法來告訴布局系統(tǒng)在未來某個時刻需要更新砂沛。系統(tǒng)會調(diào)用updateConstraints去更新布局烫扼。
updateConstraints :自定義View時,我們應(yīng)該重寫這個方法來設(shè)置當(dāng)前view局部的布局約束碍庵。重寫這個方法時映企,一定要調(diào)用[super updateConstraints]悟狱。
needsUpdateConstraints :布局系統(tǒng)使用這個返回值來確定是否調(diào)用updateConstraints
updateConstraintsIfNeeded :我們可以調(diào)用這個方法觸發(fā)update Constraints的操作。在needsUpdateConstraints返回YES時堰氓,才能成功觸發(fā)update Constraints的操作挤渐。我們不應(yīng)該重寫這個方法。
Auto Layout的布局過程是 update constraints(updateConstraints)-> layout Subviews(layoutSubViews)-> display(drawRect) 這三步不是單向的双絮,如果layout的過程中改變了constrait, 就會觸發(fā)update constraints浴麻,進(jìn)行新的一輪迭代。我們在實(shí)際代碼中囤攀,應(yīng)該避免在此造成死循環(huán)白胀。
UIConstraintBasedCompatibility
<pre style="margin: 1.5em 0px; padding: 0px; font-size: 1em; width: 712.797px; overflow: auto; background: rgb(247, 247, 247); font-style: normal; font-variant: normal; font-weight: normal; font-stretch: normal; line-height: 1.5em; font-family: "Andale Mono", "Lucida Console", monospace; white-space: pre;">
-
@
interface
UIView (UIConstraintBasedCompatibility)
- (BOOL)translatesAutoresizingMaskIntoConstraints NS_AVAILABLE_IOS(6_0);
-
- (
void
)setTranslatesAutoresizingMaskIntoConstraints:(BOOL)flag NS_AVAILABLE_IOS(6_0);
- (BOOL)requiresConstraintBasedLayout NS_AVAILABLE_IOS(6_0);
@end
</pre>
默認(rèn)情況下,View的autoresizing工作會根據(jù)當(dāng)前位置自動設(shè)置約束抚岗。我們在使用代碼寫自己的約束布局代碼時或杠,必須設(shè)置當(dāng)前View的translatesAutoresizingMaskIntoConstraints為NO,否則無法正常運(yùn)作宣蔚。IB默認(rèn)是NO向抢。
requiresConstraintBasedLayout :我們應(yīng)該在自定義View中重寫這個方法。如果我們要使用Auto Layout布局當(dāng)前視圖胚委,應(yīng)該設(shè)置為返回YES挟鸠。
UIConstraintBasedLayoutLayering
<pre style="margin: 1.5em 0px; padding: 0px; font-size: 1em; width: 712.797px; overflow: auto; background: rgb(247, 247, 247); font-style: normal; font-variant: normal; font-weight: normal; font-stretch: normal; line-height: 1.5em; font-family: "Andale Mono", "Lucida Console", monospace; white-space: pre;">
- (CGRect)alignmentRectForFrame:(CGRect)frame NS_AVAILABLE_IOS(6_0);
- (CGRect)frameForAlignmentRect:(CGRect)alignmentRect NS_AVAILABLE_IOS(6_0);
- (UIEdgeInsets)alignmentRectInsets NS_AVAILABLE_IOS(6_0);
</pre>
AutoLayout并不會直接操作View的Frame,但是視圖的alignment rect是起作用的亩冬。視圖的默認(rèn)alignmentRectInsets值就是(0,0,0,0)艘希。
我們可以簡單的對當(dāng)前View設(shè)置用來布局的矩形,比如:
我們有一個自定義icon類型的Button硅急,但是icon的大小比我們期望點(diǎn)擊的Button區(qū)域要小覆享。這個時候我們可以重寫alignmentRectInsets,把icon放在適當(dāng)?shù)奈恢谩?/p>
大多數(shù)情況下重寫alignmentRectInsets這個方法可以滿足我們的工作营袜。如果需要更加個性化的修改撒顿,我們可以重寫alignmentRectForFrame和frameForAlignmentRect這兩個方法。比如我們不想減去視圖固定的Insets荚板,而是需要基于當(dāng)前frame修改alignment rect凤壁。在重寫這兩個方法時,我們應(yīng)該確保是互為可逆的跪另。
Base line
<pre style="margin: 1.5em 0px; padding: 0px; font-size: 1em; width: 712.797px; overflow: auto; background: rgb(247, 247, 247); font-style: normal; font-variant: normal; font-weight: normal; font-stretch: normal; line-height: 1.5em; font-family: "Andale Mono", "Lucida Console", monospace; white-space: pre;">
- (UIView *)viewForBaselineLayout NS_AVAILABLE_IOS(6_0);
</pre>
當(dāng)我們在使用布局約束中NSLayoutAttributeBaseline屬性時拧抖,系統(tǒng)會默認(rèn)返回當(dāng)前視圖的底部作為baseline。我們可以重寫上述方法免绿,但必須返回的是當(dāng)前視圖中的子視圖唧席。
Intrinsic Content Size
<pre style="margin: 1.5em 0px; padding: 0px; font-size: 1em; width: 712.797px; overflow: auto; background: rgb(247, 247, 247); font-style: normal; font-variant: normal; font-weight: normal; font-stretch: normal; line-height: 1.5em; font-family: "Andale Mono", "Lucida Console", monospace; white-space: pre;">
-
UIKIT_EXTERN
const
CGFloat UIViewNoIntrinsicMetric NS_AVAILABLE_IOS(6_0);
- (CGSize)intrinsicContentSize NS_AVAILABLE_IOS(6_0);
-
- (
void
)invalidateIntrinsicContentSize NS_AVAILABLE_IOS(6_0);
- (UILayoutPriority)contentHuggingPriorityForAxis:(UILayoutConstraintAxis)axis NS_AVAILABLE_IOS(6_0);
-
- (
void
)setContentHuggingPriority:(UILayoutPriority)priority forAxis:(UILayoutConstraintAxis)axis NS_AVAILABLE_IOS(6_0);
- (UILayoutPriority)contentCompressionResistancePriorityForAxis:(UILayoutConstraintAxis)axis NS_AVAILABLE_IOS(6_0);
-
- (
void
)setContentCompressionResistancePriority:(UILayoutPriority)priority forAxis:(UILayoutConstraintAxis)axis NS_AVAILABLE_IOS(6_0);
</pre>
通過重寫intrinsicContentSize可以設(shè)置當(dāng)前視圖顯示特定內(nèi)容時的大小。比如我們設(shè)置一個自定義View,View里面包含一個Label顯示文字袱吆,為了設(shè)置當(dāng)前View在不同Size Class下內(nèi)容的大小厌衙,我們可以這樣:
<pre style="margin: 1.5em 0px; padding: 0px; font-size: 1em; width: 712.797px; overflow: auto; background: rgb(247, 247, 247); font-style: normal; font-variant: normal; font-weight: normal; font-stretch: normal; line-height: 1.5em; font-family: "Andale Mono", "Lucida Console", monospace; white-space: pre;">
- (CGSize)intrinsicContentSize
{
CGSize size = [label intrinsicContentSize];
-
if
(self.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassCompact) {
size.width += 4.0f;
-
}
else
{
size.width += 40.0f;
}
-
if
(self.traitCollection.verticalSizeClass == UIUserInterfaceSizeClassCompact) {
size.height += 4.0;
}
else
{
size.height += 40.0;
}
-
return
size;
}
</pre>
當(dāng)有任何會影響這個Label內(nèi)容大小的事件發(fā)生時,我們應(yīng)該調(diào)用invalidateIntrinsicContentSize:
<pre style="margin: 1.5em 0px; padding: 0px; font-size: 1em; width: 712.797px; overflow: auto; background: rgb(247, 247, 247); font-style: normal; font-variant: normal; font-weight: normal; font-stretch: normal; line-height: 1.5em; font-family: "Andale Mono", "Lucida Console", monospace; white-space: pre;">
-
label.text = @
"content update"
[self invalidateIntrinsicContentSize];
// 或者比如當(dāng)前視圖Size Class改變的時候
-
- (
void
)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection
{
-
[
super
traitCollectionDidChange:previousTraitCollection];
-
if
((self.traitCollection.verticalSizeClass != previousTraitCollection.verticalSizeClass)
|| (self.traitCollection.horizontalSizeClass != previousTraitCollection.horizontalSizeClass)) {
[self invalidateIntrinsicContentSize];
}
}
</pre>
不是所有的視圖都有 intrinsicContentSize绞绒, UIView默認(rèn)情況下就返回的是 UIViewNoIntrinsicMetric婶希。只有當(dāng)視圖中需要根據(jù)內(nèi)部內(nèi)容進(jìn)行調(diào)整大小時,我們才需要用到 intrinsicContentSize蓬衡。
當(dāng)視圖大小在變化時喻杈,我們可以使用上面最后四個API來設(shè)置視圖的壓縮或者放大的方式。
<pre style="margin: 1.5em 0px; padding: 0px; font-size: 1em; width: 712.797px; overflow: auto; background: rgb(247, 247, 247); font-style: normal; font-variant: normal; font-weight: normal; font-stretch: normal; line-height: 1.5em; font-family: "Andale Mono", "Lucida Console", monospace; white-space: pre;">
typedef NS_ENUM(NSInteger, UILayoutConstraintAxis) {
UILayoutConstraintAxisHorizontal = 0,
UILayoutConstraintAxisVertical = 1
};
</pre>
上面最后四個API主要是通過修改水平或者垂直方向的優(yōu)先級來實(shí)現(xiàn)視圖是基于水平縮小(放大)還是垂直縮小(放大)狰晚。當(dāng)我們的視圖需要根據(jù)內(nèi)部內(nèi)容進(jìn)行調(diào)整大小時筒饰,我們應(yīng)該使用上述方法為當(dāng)前視圖設(shè)置初始值。而不應(yīng)該重寫這幾個方法壁晒。
UIConstraintBasedLayoutFittingSize
<pre style="margin: 1.5em 0px; padding: 0px; font-size: 1em; width: 712.797px; overflow: auto; background: rgb(247, 247, 247); font-style: normal; font-variant: normal; font-weight: normal; font-stretch: normal; line-height: 1.5em; font-family: "Andale Mono", "Lucida Console", monospace; white-space: pre;">
-
UIKIT_EXTERN
const
CGSize UILayoutFittingCompressedSize NS_AVAILABLE_IOS(6_0);
-
UIKIT_EXTERN
const
CGSize UILayoutFittingExpandedSize NS_AVAILABLE_IOS(6_0);
-
@
interface
UIView (UIConstraintBasedLayoutFittingSize)
- (CGSize)systemLayoutSizeFittingSize:(CGSize)targetSize NS_AVAILABLE_IOS(6_0);
- (CGSize)systemLayoutSizeFittingSize:(CGSize)targetSize withHorizontalFittingPriority:(UILayoutPriority)horizontalFittingPriority verticalFittingPriority:(UILayoutPriority)verticalFittingPriority NS_AVAILABLE_IOS(8_0);
@end
</pre>
上面兩個API可以獲得當(dāng)前使用AutoLayout視圖的size瓷们。其中targetSize可以傳入UILayoutFittingCompressedSize或者UILayoutFittingExpandedSize,分別對應(yīng)的是最小情況下可能的Size和最大情況下可能的Size秒咐。
UIConstraintBasedLayoutDebugging
<pre style="margin: 1.5em 0px; padding: 0px; font-size: 1em; width: 712.797px; overflow: auto; background: rgb(247, 247, 247); font-style: normal; font-variant: normal; font-weight: normal; font-stretch: normal; line-height: 1.5em; font-family: "Andale Mono", "Lucida Console", monospace; white-space: pre;">
- (NSArray *)constraintsAffectingLayoutForAxis:(UILayoutConstraintAxis)axis NS_AVAILABLE_IOS(6_0);
- (BOOL)hasAmbiguousLayout NS_AVAILABLE_IOS(6_0);
-
- (
void
)exerciseAmbiguityInLayout NS_AVAILABLE_IOS(6_0);
</pre>
第一個API可以獲得視圖在不同方向上所有的布局約束谬晕。
hasAmbiguousLayout :可以知道當(dāng)前視圖的布局是否會有歧義。這里有一個私有API _autolayoutTrace可以獲得整個視圖樹的字符串携取。
<pre style="margin: 1.5em 0px; padding: 0px; font-size: 1em; width: 712.797px; overflow: auto; background: rgb(247, 247, 247); font-style: normal; font-variant: normal; font-weight: normal; font-stretch: normal; line-height: 1.5em; font-family: "Andale Mono", "Lucida Console", monospace; white-space: pre;">
-
ifdef DEBUG
-
NSLog(@
"%@"
, [self performSelector:@selector(_autolayoutTrace)]);
-
endif
</pre>
exerciseAmbiguityInLayout :這個方法會隨機(jī)改變視圖的layout到另外一個有效的layout攒钳。這樣我們就可以很清楚的看到哪一個layout導(dǎo)致了整體的布局約束出現(xiàn)了錯誤,或者我們應(yīng)該增加更多的布局約束雷滋。
我們應(yīng)該讓上面的四個方法只在DEBUG環(huán)境下被調(diào)用不撑。
新增支持 UITraitEnvironment Protocol
<pre style="margin: 1.5em 0px; padding: 0px; font-size: 1em; width: 712.797px; overflow: auto; background: rgb(247, 247, 247); font-style: normal; font-variant: normal; font-weight: normal; font-stretch: normal; line-height: 1.5em; font-family: "Andale Mono", "Lucida Console", monospace; white-space: pre;">
@protocol UITraitEnvironment
@property (nonatomic, readonly) UITraitCollection *traitCollection;
-
- (
void
)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection;
@end
</pre>
iOS 8 上新增了Size Class的概念,其中UITraitCollection類用來描述不同Size大小晤斩。關(guān)于Size Class和UITraitCollection的概念可參考如下鏈接:http://joywii.github.io/blog/2014/09/24/ios8-size-classesde-li-jie-yu-shi-yong/
UIView實(shí)現(xiàn)了這個協(xié)議焕檬,我們可以獲得當(dāng)前View的traitCollection,從而得知當(dāng)前View處于什么樣的Size Class下尸昧。并且當(dāng)traitCollection有變化時揩页,我們可以通過重寫traitCollectionDidChange知道該事件的觸發(fā)。默認(rèn)情況下烹俗,這個方法什么都不執(zhí)行。
traitCollection的變化是從UIScreen開始被觸發(fā)萍程,并且逐層往下傳遞的幢妄,具體如下:
UIScreen -> UIWindow -> UIViewController -> ChildViewControllers -> View -> Subviews
關(guān)于這一點(diǎn),我在詳解UICoordinateSpace和UIScreen在iOS 8上的坐標(biāo)問題一文中有做詳細(xì)解釋茫负。
總結(jié) UIView到目前為止(iOS 8.1)蕉鸳,所有增加的關(guān)于AutoLayout的API請參考上述文章。進(jìn)一步對這些API理解可以讓我們寫出更健壯的布局代碼。