iOS里面可以说UIView是最重要的类之一,所有界面上的控件都是UIView的子类,UIView继承自UIResponder,UIResponder负责处理事件,而 UIResponder继承自NSObject。NSObject是Cocoa里面绝大部分类的根类。view hierarchy 是view最重要的组织形式,一个view可以拥有subviews, 而一个subview也有一个superview。view hierarchy使view一起出现和移动,当一个view移动时,它的所有subviews也一起移动;当一个view 被删除后,它的所有subviews也被一起删除。
The Window
view hierarchy的顶层元素是app的window,它是UIWindow的一个实例,并继承自UIView。app应该只有一个main window,而且一个屏幕只能 有一个window,它是界面上有效view的最终superview。window必须将设备的屏幕填充满:
UIWindow *w = [[UIWindow alloc] initWithFrame:[[UIScreen maniScreen] bounds]];
- App 使用 main storyboard
如果你的app使用main storyboard,那么UIApplicationMain会自动实例化一个正确的
UIWindow并将它赋值给app delegate的window属性,然后它会实例化storyboard的initial
view controller并将它赋值给window的rootViewController属性。这些都在app调用
application:didFinnishLaunchingWithOptions
之前完成。
最后,UIApplicationMain会调用makeKeyAndVisible
来显示app的界面,这些在调用application:didFinnishLaunchingWithOptions
之后完成。
如果你想子类化window时,你必须在AppDelegate.m文件里面重写window属性的getter函数:
- (UIWindow*) window {
UIWindow* w = self->_window;
if (!w) {
w = [[MyWindow alloc] initWithFrame:
[[UIScreen mainScreen] bounds]];
self->_window = w;
}
return w;
}
- App 不使用 main storyboard
如果你的app没有main storyboard,通常使用代码来完成:
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
如果你想引用window属性,可以使用application的delegate中的window属性
UIWindow* w = [[[UIApplication sharedApplication] delegate] window]
SuperView 和 SubView
- 如果一个view在它的superview上移除或移动,它的所有subview也一起移除或移动。
- 一个view的透明度将会被它的subview继承。
- 如果view的subview的范围超出view的范围,可以不显示subview超出范围部分,这被称为clipping,通过clipsToBounds属性设置。
- superview负责管理它的subview,当一个subview不再属于它的superview或superview本身被释放时,superview负责释放它的subview。
- 当view的尺寸被改变时,它的subview也可以自动改变。
一个UIView有superview属性和subviews属性,isDescendantOfView:
可以检测一个view是否
是另一个view的subview,不管深度有多深。如果你想引用一个特殊的view,你可以通过outlet,
或通过设置view的tag属性,然后使用viewWithTag:
方法获得。
addSubview:
方法使一个view成为另一个的subview,removeFromSuperview
方法将一个view
从它的superview的view hierarchy中移除。记住,一个view从它superview移除后,view
将会被释放,如果你还想使用它,你必须先保持(retain)它,通常通过设置属性的retain来完成。
当这些改变发生时,会发送事件通知,你需要通过子类化来重写这些事件,以处理事件。
didAddSubview:
willRemoveSubview:
didMoveToSuperview:
willMoveToSuperview:
didMoveToWindow:
willMoveToWindow:
addSubview
会将view放在superview的最前端,你也可以通过下面的方法,将view插入到view
hierarchy的任意位置:
insertSubview:atIndex:
insertSubview:belowSubvie:
insertSubview:aboveSubview:
exchangeSubviewAtIndex:withSubviewAtIndex:
bringSubviewToFont:
sendSubviewToBack:
你可以通过下面的方法,删除所有subview。
for(UIView *v in view.subviews)
[v removeFromSuperview];
或
[view.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
隐藏和透明
通过设置UIView的属性hidden来隐藏view,当view隐藏后通常它不会再接收事件。
通过设置backgroundColor属性来设置view的背景色,当backgroundColor的属性为nil时, 它拥有一个透明背景。当它的属性为nil时,它可以作为其他views的superview,这样可以使 这些views在同一个view hierarchy中。
通过设置alpha属性,可以改变view的透明度(1.0表示不透明,0.0表示完全透明)。这将会 影响它的subviews,如果superview的alpha为0.5,那么它的subviews的alpha都不会超过0.5。 (更复杂的是颜色也有alpha属性,例如,一个view的alpha为1.0,它也可以拥有一个透明背景, 因为,它的backgroundColor的alpha小于1.0)。一个view完全透明,相当于设置hidden为YES, 它和它的subviews都会不可见,通常也不会在接收事件。
view的alpha属性同样会影响它背景色的可见度和它内容的可见度。例如,如果一个view的alpha 小于1,它显示一个图像且有背景色,背景色将会透过图像显示出来(view后面的任何东西都会 穿透显示)。
view的opaque属性,不会影响view透明度,它对绘制(drawing)系统有影响。当一个view 完全填充它的边界范围,且它的opaque为YES,且alpha属性为1.0,那么提高绘制系统的性能。
Frame
view的frame属性,表示view在其superview坐标系中的位置和大小。坐标原点在左上角, 向右为x轴,向下为y轴。
Bounds and Center
bounds属性表示view在自己的坐标系中的位置和大小,CGRectInset
可以很方便的
将一个view插入到另一个view中:
UIView *v2 = [[UIView alloc] initWithFrame:CGRectInset(v1.bounds, 10, 10)];
改变view的bounds会影响它的frame,同样改变frame会影响bounds,但是改变bounds不会影响 view的center属性。center属性就像frame属性一样,代表view在其superview坐标系中位置, 但是它是bounds的中心位置,它的值就像如图下面表示:
CGPoint center = CGPointMake(CGRectGetMidX(theView.bounds),
CGRectGetMidY(theView.bounds));
view 的center是连接view的bounds和它superview的bounds的一个点,改变view的bounds不会改变center,改变center也不会改变bounds。
view的bounds和center可以清楚的描述view的大小和它在superview中的位置。view的frame属性是多余的, 实际上,frame仅仅用于方便的表达center和bounds的值。使用bounds和center来表示view的大小和它在 superview中所处的位置,更加的合理和可配置。
每个view都有自己的坐标系(boudns),自己的坐标系和superview坐标系的关系(center)。我们可以转换 在同一个窗口中任意两个view的坐标系,转换支持CGPoint和CGRect之间的双向转换。
convertPoint:fromVIew:
convertPoint:toView:
convertRect:fromView:
convertRect:toView:
如果,第二个参数为nil,表示window。
例如,如果v2是v1的subview,将v2放在v1的中间,可以使用:
v2.center = [v1 convertPoint:v1.center fromView:v1.superview];
当通过center来改变view的位置时,如果view的高或宽不是整数,view将会misaligned,view会显示不正 确。你可以在Simulator中通过Debug->Color Misaligned Images来检查。
Transfom
view 的transform属性,在不改变view的bounds和center的前提下,改变view的大小和方向。transform是一个
CGAffineTransform类型,它相当于6个3*3的矩阵,详细信息参考
“Transforms” chapter of Apple’s Quartz 2D Pro‐ gramming Guide, especially the section called “The Math Behind the Matrices.”
但是,你不需要知道这些细节,以CGAffineTransformMake
开头的方法,提供了3个基本transform类型:旋转、放大缩小
、移动(translation)。第四种transform类型,比如倾斜、劈开,没有方便函数可供调用。
默认,view的变换矩阵被称为CGAffineTransformIndentity,应用任何transform都围绕center,center始终 保持不变。
UIView* v1 = [[UIView alloc] initWithFrame:CGRectMake(113, 111, 132, 194)];
v1.backgroundColor = [UIColor colorWithRed:1 green:.4 blue:1 alpha:1];
UIView* v2 = [[UIView alloc] initWithFrame:CGRectInset(v1.bounds, 10, 10)];
v2.backgroundColor = [UIColor colorWithRed:.5 green:1 blue:0 alpha:1];
[mainview addSubview: v1];
[v1 addSubview: v2];
v1.transform = CGAffineTransformMakeRotation(45 * M_PI/180.0);
上面演示了将v1旋转45度,v1的center和bounds都没有改变,所以v1的坐标系没有改变,v1的subview v2也 旋转45度,保持v2和v1的相对位置不变。这儿的规则是,如果view的transform不是identity transform, 那么你不应该设置它的frame。
v1.transform = CGAffineTransformMakeScale(1.8,1);
对v1的bounds和center同样没有影响,所以subview同样保持和v1相对位置不变。这意味着,v1和v2被同时 缩放。
应用给一个view的transform可以方便的给另一个view使用,这些方法的名字不不包含“Make”。这些方法不是 无序的,它们是有序的。
UIView* v1 = [[UIView alloc] initWithFrame:CGRectMake(20, 111, 132, 194)];
v1.backgroundColor = [UIColor colorWithRed:1 green:.4 blue:1 alpha:1];
UIView* v2 = [[UIView alloc] initWithFrame:v1.bounds];
v2.backgroundColor = [UIColor colorWithRed:.5 green:1 blue:0 alpha:1];
[mainview addSubview: v1];
[v1 addSubview: v2];
先右移100再旋转45度:
v2.transform = CGAffineTransformMakeTranslation(100,0);
v2.transform = CGAffineTransformRotate(v2.transform, 45 * M_PI / 180.0);
和先旋转45度再右移100:
v2.transform = CGAffineTransformMakeRotation(45 * M_PI / 180.0);
v2.transform = CGAffineTransformTranslate(v2.transform, 100.0);
会得到不同的图形。
CGAffineTransformConcat
方法使transform矩阵相乘,这也是有序的。先旋转45度再右移100也
可以使用下面的方法:
CGAffineTransform r = CGAffineTransformMakeRotation(45 * M_PI / 180.0);
CGAffineTransform t = CGAffineTransformMakeTranslation(100, 0);
v2.transform = CGAffineTransformConcat(t,r); //not r,t
将已经存在的transform移除,使用对应的invert方法。例如,先旋转45度再右移100,最后移除旋转。
CGAffineTransform r = CGAffineTransformMakeRotation(45 * M_PI / 180.0);
CGAffineTransform t = CGAffineTransformMakeTranslation(100, 0);
v2.transform = CGAffineTransformConcat(t, r);
v2.transform = CGAffineTransformConcat(CGAffineTransformInvert(r), v2.transform);
最后,没有方便的方法使图形倾斜,可以使用下面的方式:
v1.transform = CGAffineTransformMake(1, 0, -0.2, 1.0, 0, 0);
transform可以应用再iOS的核心界面,window的frame和bounds都被锁定到屏幕,不能更改,但是root view 的frame和bounds可以被更改。当设备被旋转90度时,root view也被旋转了90度,{0,0}点移动到了用户的 右上角。root view的subviews在root view的坐标系中,拥有自己的frame,所以它们可以被正确的显示。
另外,root view的bounds的高和宽被交换,所以可能导致某些界面元素不能被显示出来,请查看下一节如何 解决这个问题。
layout
我们已经知道,当superview的bounds的origin改变时,subview会移动。但是,当superview的bounds的size改变时, subview的bounds和center都不会改变,superview的bounds的origin也未改变,所以 subview保持在superview左上角的相对位置不变。在实际情况中,我们也许想要当superview的bounds的 size发生变化时,subview也能跟着变化,这被称为layout。
layout 有三种方法:
- Manual layout
当superview改变尺寸时,它会调用layoutSubviews
方法。提供自己的subclass和重写layoutSubviews
可以手动布局subview。
- Autoresizing
Autoresizing是iOS6之前的自动布局方法,当superview的尺寸变化时,subview根据它的属性autoresizingMask来调整自己尺寸。
- Autolayout
Autolayout在iOS6引进(和前面的系统不兼容),通过view的constraints设置。constraint不仅可以 用于view,还可以用于描述任意两个view之间的关系。constrain可以使你不通过代码,也能实现更加 复杂精确的layoutSubviews函数效果。
你也可以混用3种layout策略,需要manual layout的情况比较少见。Autoresizing自动使用,除非你 设置superview的autoresizeSubviews属性为NO,或者你在view种使用autolayout替代。Autolayout 是一个可选的技术,你可以用于任何你认为合适的地方,你也可以和autoresizing一起使用。
在Xcode5的storyboard和xib文件中默认使用autolayout,你可以在File inspector中选中或取消使用。 另一方面,你在代码中创建的界面,默认使用autoresizing,而不是autolayout。
Autoresizing
Autoresizing概念上相当于subview的“springs and struts”。spring 可以伸缩,strut不可以,springs和struts可以被设定为 内部的、外部的、水平的、垂直的。因此你可以通过内部springs和structs设置 view的缩放,使用外部springs和struts设置view的位置。例如:
1.一个subview在superview的中间并且保持在中间,但是当superview的size改变时,它的size也改变。 它拥有外部struct和内部spring。
2.一个subview在superview的中间并且保持,当superview的size改变时,它的size不改变。那么它 拥有内部struct和外部spring。
3.一个OK按钮始终在superview的右下角。那么它拥有内部的structs,外部的struts到它的右边和下边 ,外部springs到它的上边和左边。
4.一个文本输入框在superview的上面,当superview变宽时它也变宽。那么它拥有外部struts,内部的 垂直strut和水平spring。
在storyboard和xib中使用autoresizing,你需要先在File inspector中取消选中“Use Auto Layout”。 你可以使用Size inspector来设置view的spring和strut,外部的实线表示struct,内部的实线表示 spring。
在代码中,通过设置view的autoresizingMask属性,这是一个位属性,使用或操作来混用。可以使用
以UIViewAutoresizingFlexible
开头的值来设置spring,不能用于设置strut。默认的UIViewAutoresizingNone,表示所有的strut,
但是实际上它不是所有的strut,它相当于UIViewAutoresizingFlexibleRightMargin和UIViewAutoresizingFlexibleBottomMargin.
UIView* v1 = [[UIView alloc] initWithFrame:CGRectMake(100, 111, 132, 194)];
v1.backgroundColor = [UIColor colorWithRed:1 green:.4 blue:1 alpha:1];
UIView* v2 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 132, 10)];
v2.backgroundColor = [UIColor colorWithRed:.5 green:1 blue:0 alpha:1];
UIView* v3 = [[UIView alloc] initWithFrame:CGRectMake(
v1.bounds.size.width-20,
v1.bounds.size.height-20,
20, 20)];
v3.backgroundColor = [UIColor colorWithRed:1 green:0 blue:0 alpha:1];
[mainview addSubview: v1];
[v1 addSubview: v2];
v1 addSubview: v3];
v2.autoresizingMask = UIViewAutoresizingFlexibleWidth;
v3.autoresizingMask = UIViewAutoresizingFlexibleTopMargin |
UIViewAutoresizingFlexibleLeftMargin;
CGRect r = v1.bounds;
r.size.width += 40;
r.size.height -= 50;
v1.bounds = r;
上面的代码中,当v1的size改变时,v2的宽度也会跟着改变,v3的大小不会改变,它会一直在v1的右下角位置不变。
当你旋转屏幕后,你的view也应该正确的旋转:
v1.frame = mainview.bounds;
v1.autoresizingMask = UIViewAutoresizingFlexibleHeight |
UIViewAutoresizingFlexibleWidth;
Autoresizing简单有效,但是有时它太简单了。它只能描述suview和superview之间的关系。layoutSubviews
,
方法在autoresizing后调用,所以你的layoutSubviews
可以修正和改变autoresizing没有正确处理的部分。
Autolayout
Autolayout可能在如下三种情况发生时使用:
- 你的代码中,向view加入autolayout constraint。
- 你的app中nib或storyboard选中“Use Auto Layout”,每个nib的view实例将使用autolayout。
- 在你的UIView子类中,重写方法
requiresConstraintBasedLayout
返回YES。
第3中方法,主要用于在切换到autolayout,并在代码添加constraints。通常通过实现view的
updateConstraints
方法,在代码中添加constrains,但是如果requiresConstraintBasedLayout
返回NO,那么将不会调用updateConstraints
。
一个superview使用autolayout,它的一个或所有subview可以不使用。但是,autolayout会穿透superview 链,所以一个view使用autolayout,那么它的所有superview都会使用autolayout。如果这些view中 有一个view是view controller的main view,那么view controller也会使用autolayout。
Constraints
Autolayout constraints 是一个NSLayoutConstraint实例,用于描述view的绝对高度和宽度,或者 描述一个view和另一个view之间的关系,两个view之间只要有共同的祖先皆可。
NSLayoutConstraint重要的属性如下:
1.firstItem, firstAttribute, secondItem, secondAttribute 这个constrain包含两个view各自的属性,如果constrain描述的是view的绝对高度和宽度,第二个view 位nil,第二个view的属性是NSLayoutAttributeNotAnAttribute,其余的属性值:
- NSLayoutAttributeLeft,NSLayoutAttributeRight
- NSLayoutAttributeTop,NSLayoutAttributeBottom
- NSLayoutAttributeLeading,NSLayoutAttributeTrailing
- NSLayoutAttributeWidth,NSLayoutAttributeHeight
- NSLayoutAttributeCenterX,NSLayoutAttributeCenterY
- NSLayoutAttributeBaseline
NSLayoutAttributeLeading、NSLayoutAttributeTrailing相当于国际上的NSLayoutAttributeLeft、 NSLayoutAttributeRight,在书写顺序为从右往左国家,它会自动翻转顺序。 2.multiplier, constant 用于设定第一个属性的值,比如满足等式“第一个属性的值=第二个属性的值 * multiplier + constant” 3.relation 用于设置第2点中等式的操作符,操作符可以可能是等号(NSLayoutRelationEqual),也可能是不等式 NSLayoutRelationLessThanOrEqual, NSLayoutRelationGreaterThanOrEqual 4.priority 优先级从1到1000
一个view可以有多个constraint,UIView有一个constraints属性,通过下面方法添加或删除constrain
- addConstraint:,addConstraints:
- removeConstraint:,removeConstraints:
对于给出的一个constrain,它属于谁呢?它属于它们两个view最近的superview。因此,如果constrain 指示view的绝对高度,那么它属于这个view。如果它描述两个同级view的关系,那么它属于这个两个view的 superview。如果它描述view和它superview之间的关系,那么它属于superview。
NSLayoutConstraint的属性除了priority和constant都是只读的,如果你想改变已经存在的constraint, 你必须先删除它,再重新添加。
Autoresizing constraints
view的autolayout机制,可能会导致在以前没有使用autolayout的view中引入autolayout。因此,必须 有一种方式,保证当view引入autolayout的时候,它和通过设置frame和autoresizingMask的位置相同。 系统有一个隐士的NSAutoresizingMaskLayoutConstraint来转换为正确的contraint。
例如,你有一个UILabel的frame是{{20,20},{42,22}},autoresizingMask为UIViewAutoresizingNone, 如果它突然加入autolayout,它superview会转换它的隐士constrain,它的宽为42、高为22,center X为41, center Y为31.
这个转换发生在view的translatesAutoresizingMaskIntoConstraints属性为YES。如果你想要添加 任何清晰的constrain到view,你需要设置translatesAutoresizingMaskIntoConstraints为NO。 否则可能会产生冲突。
constraints错误
主要的错误有如下两种:
- 冲突
当contraint不能被同时满足时,就会产生冲突。
- 不充分
view使用autolayout,但是没有提供足够多的信息来确定view的位置和尺寸。这是一个不被容易发现的错误。
为了分析不充分的情况,可以打印view的hasAmbiguousLayout属性,请确保在提交App Store之前删除。 写一个NSLayoutConstraint的类别,可以很方便的检查view的不充分情况。
@implementation NSLayoutConstraint (Ambiguity)
+ (void) reportAmbiguity:(UIView*) v {
if (nil == v)
v = [[UIApplication sharedApplication] keyWindow];
for (UIView* vv in v.subviews) {
NSLog(@"%@ %d", vv, vv.hasAmbiguousLayout);
if (vv.subviews.count)
} }
@end
另外,也可以在调试窗口访问key window的_autolayoutTrace,可以清晰输出view的情况。
(lldb) po [[UIWindow keyWindow] _autolayoutTrace]
(id) $1 = 0x074a41a0
*<UIWindow:0x749b890>
| *<UIView:0x749ccb0>
| | *<UIView:0x749c280>
| | | *<UIView:0x749c790>
| | | *<UIView:0x749c930> - AMBIGUOUS LAYOUT
为了更好的放置一个view到superview,可以调用UIView的constraintsAffectingLayoutForAxis:
方法获得它所有的constrains,你可以选择坐标轴UILayoutConstraintAxisHorizontal和
UILayoutConstraintAxisVertical,但是你可能更喜欢用0和1代替它们。如果一个view未使用
autolayout,那么它返回一个空的数组。这儿同样使用类别
@implementation NSLayoutConstraint (Listing)
+ (void) listConstraints:(UIView*) v {
if (nil == v)
v = [[UIApplication sharedApplication] keyWindow];
for (UIView* vv in v.subviews) {
NSArray* arr1 = [vv constraintsAffectingLayoutForAxis:0];
NSArray* arr2 = [vv constraintsAffectingLayoutForAxis:1];
NSLog(@"%@\nH: %@\nV:%@", vv, arr1, arr2);
if (vv.subviews.count)
} }
@end
从冲突和不充分的观点,我们可以理解constraint的顺序。想象所有的constraint都被放在一个盒子里面, 每个盒子都是一个优先级,他们从高到低放置。第一个盒子(1000)放置所有需要被满足的constraint, 它被首先遵守(如果他们之间冲突,这是很坏的情况,会被报道在调试窗口。同时,系统会调低冲突中其中 一个constraint,然后不遵守它,以满足其他constraint要求)。如果任然没有足够的信息来完成排布, 那么系统将使用下一个盒子,然后遵守它,这样下去直到满足要求。除了第一个盒子,我们不需要关心其他 盒子的constraint是否被正确遵守,因为如果这个盒子残留不充分,那么它将使用下一个优先级的盒子来 补充满足。
固有内容尺寸
有一些控件使用autolayout时,有一个或多个继承尺寸,所以当它们没有NSLayoutConstraint时也能正确 的显示。其实,继承的尺寸暗中使用NSContentSizeLayoutConstraint类生成constraint。例如,按钮 有一个标准高度,它的宽度取决于它的标题。这继承的尺寸是对象固有内容尺寸。
你给控件指定的constraint不能和其固有内容尺寸冲突:
- contentHuggingPriorityForAxis
view的尺寸应该小于等于它自己的内容固有尺寸优先级(默认250)。
- contentCompressionResistancePriorityForAxis
view的尺寸应该大于等于自己的内容固有尺寸的优先级(默认750)。
例如,有两个label平放在superview中:
@"V:|-[_lab1]"
@"V:|-[_lab2]"
@"H:|-20-[_lab1]"
@"H:[_lab2]-20-|"
@"H:[_lab1]-(>=20)-[_lab2]"
当两个label变得越来越长时,还应该指定谁应该被压缩,下面指定当lab1和lab2相遇时,lab2应该被压缩。
[self.lab1 setContentCompressionResistancePriority:751
forAxis:UILayoutConstraintAxisHorizontal];
下面的代码中,button位于superview的中间位置,label位于它的左边,当label的长度越来越长的时候, button会向右移动。
self.button.translatesAutoresizingMaskIntoConstraints = NO;
self.label.translatesAutoresizingMaskIntoConstraints = NO;
NSDictionary* d = NSDictionaryOfVariableBindings(_button,_label);
[self.view addConstraints:
[NSLayoutConstraint
constraintsWithVisualFormat:@"V:[_button]-(112)-|"
options:0 metrics:nil views:d]];
[self.view addConstraints:
[NSLayoutConstraint
constraintsWithVisualFormat:@"H:|-(>=10)-[_label]-[_button]-(>=10)-|"
options:NSLayoutFormatAlignAllBaseline metrics:nil views:d]];
NSLayoutConstraint* con =
[NSLayoutConstraint
constraintWithItem:_button attribute:NSLayoutAttributeCenterX
relatedBy:0
toItem:self.view attribute:NSLayoutAttributeCenterX
multiplier:1 constant:0];
con.priority = 700; // try commenting this out to see the difference
[self.view addConstraint:con];
你可以重写自定义UIView的intrinsicContentSize
的方法,返回你自定义UIView的固有内容长度。你
的自定义UIView的尺寸和它的内容相关时,你才需要这么做。当内容尺寸改变的时候你需要刷新你的view,
你向你的view发送invalidateIntrinsicContentSize
消息,它会再次调用intrinsicContentSize
。
有时,你需要你的view能和别的view基线对齐,为了提供你view的基线,你需要包含一个subview,这个
subview的底部将是你view的基线,你需要viewForBaselineLayout
方法中返回subview。
在nib中使用autolayout
layout 事件顺序
1.view和它所有的subview将会从最底层(最深的subview)到最顶层(view本身)将会调用updateConstraints
。
如果subview的constrain没有变化,可能不会调用这个方法,但是最顶层的view肯定会调用。
你可以重写updateConstraints
方法,但是你不要直接调用它。为了触发调用它,你可以给view发送
updateConstraintsIfNeeded
消息,但是除非constrain已经改变或这个是顶层view,才会触发调用
updateConstraints
。如果你需要强制调用它,发送setNeedsUpdateConstraints
消息。
2.view和它所有的subview将从最顶层(view本身)到最底层(最深的subview)将会调用layoutSubviews
.
你可以重写这个方法,用于手动布局。layoutSubviews
可以使你在autoresizing发生后,进行手动布局。
你不能直接调用这个方法,为了触发它,你可以给view发送layoutIfNeeded
消息(这可能引起整个
view树(不仅这个view上面或下面的view树)layout)。但通常发送setNeedsLayout
消息,在代码完成
后触发layoutSubviews
。
使用autolayout时,如果你想稍微修改一下布局的结果,你可以重写layoutSubviews
。首先,你调用
super,让subview有它们自己的frame,然后你检查它们的frame,如果你不喜欢它们,你可以删除subview、
添加或删除constrain等等,最后再次调用super,得到新的结果。
除非你明确表示要立即layout,否则它们只会在代码完成且需要更新的时候才会layout。
Autolayout和Transform
当你运行如下代码:
v.transform = CGAffineTransformMakeScale(1.2,1.2);
我希望view围绕着center向四周放大,但是这个view会向下和向右放大,而它的向左和向上保持不变,也 就是我希望它的center不变,结果它的原点不变,center改变。这都取决于view的constrain。
autolayout不能很好的应用在transform中,主要有如下原因:
- 应用transform会导致view立即触发layout
- 当layout触发时,
layoutSubviews
通过constrain设置view的frame。
因此,当你给一个使用autolayout的view应用transform的时候,autolayout会改变view的frame来 阻止transform的改变,这好像是一个bug。
这个问题不好解决,这儿有三个建议,但是都不是令人很满意:
- 让你constrain更完善,到你要给一个view应用transform的时候,设置view的constrain影响center,而不是frame。
- 创建一个主view和一个subview。主view的位置通过constrain设置,并且不可见。在subview中删除
所有能影响它的constrain,设置
translatesAutoresizingMaskIntoConstraints
为YES。 - 使用layer transform,因为layer transform不会触发layout。