ps一下,由于autolayout设定了约束,但是想让一个View从一点到另外一点,代码中直接修改view的frame是行不通的
oginViewTopConstraint = [NSLayoutConstraint constraintWithItem:self.loginView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:20];//定义新约束
[self.view removeConstraint:self.topConstraint];//移除旧约束
[self.view addConstraint:loginViewTopConstraint];//添加新约束
[self.view layoutIfNeeded];//使约束生效
如果添加一个button,或者label,而不设置任何constraint Ib会自动生成constraints,而这些constraints 是fixed 是无法根据intrinsic content size 的变化而变化。
添加一个button后,调用invalidateInstrinsicContentSize,在执行前后,分别打印constraints
主要是调试:
NSLog(@”current constraints %@ “, [[self textLabel] constraintsAffectingLayoutForAxis:UILayoutConstraintAxisHorizontal]);(水平)
NSLog(@”current constraints %@ “, [[self textLabel] constraintsAffectingLayoutForAxis:UILayoutConstraintAxisVertical]);(垂直)
[[self textLabel] setText:@”weuituwjaskdtuqwieotuasdjgakleutaiosgajksdgequwtioasdjgkasjdgkajskgd”];
[[self textLabel] invalidateIntrinsicContentSize];
NSLog(@”current constraints %@ “, [[self textLabel] constraintsAffectingLayoutForAxis:UILayoutConstraintAxisHorizontal]);
NSLog(@”current constraints %@ “, [[self textLabel] constraintsAffectingLayoutForAxis:UILayoutConstraintAxisVertical]);
可以看出IB产生的是IB auto generated at build time for view with fixed frame,无论intrinsic content size如何变化,都无济于事。所以,在使用AutoLayout时,切记:每个元素都应该有能完全确定size和位置的constraints, 如果有一样不能确定,IB都会提示Error 或者Warning,而这些Error或者Warning也是必须解决的。就是宽高的约束,上下左右的约束。把这些想成一个组合,
关于两种PlaceHolder
placeHolder Constraits 和placeholder instrinsic content size
placeholder constraits是说这个constraint只是个placeholder,在build时就会被去掉,这样可以阻止IB自动生成constraints;placeholder instrinsic content size是說在IB中设计时使用的size,在runtime时则需要调用intrinsicContentSize方法来确定。(这个我自己要找找看)
instrinsicContentSize 和constraint 的关系:instrinsicContentSize 和constraint是Auto Layout的支柱概念,缺一不可,
通常unambiguous layout generally requires setting two attributes in each axis(也就是在一个维度上需要设置起点和长度两个信息)
. When a view has an intrinsic content size, that size accounts for one of the two attributes.(如果一个view 有intrinsicContentSize,并且设计者想这样,intrinsicContentSize是可以起到那个长度的限制作用的
You can, for example, place a text-based control or an image view in the center of its superview, and its layout will not be ambiguous. The intrinsic content size plus the location combine for a fully specified placement. 能够这样的前提是1. 使用系统的标准View,2. 在IB中指定Intrinsic size时需要用Default(System defined)
比如如果只指定位置信息,不指定size,使用system default intrinsic size,时,输出时这样的
size信息部分是由IB自动生成的,但是它们的priority只有251,而不是之前的1000(required)了。
只指定位置,对比Default 和 PlaceHolder的 constraints,可以看到,在使用Default时,IB给自动加了NSContentSizeLayoutConstraint;在使用Placeholder时,IB自动生成了NSIBPrototypingLayoutConstraint,因为我们使用了PlaceHolder,并且size都是0,所以IB已经无法知道size信息了,为了避免ambiguity就只能自动加了。
我们在使用custom view时,需要指定intrinsic size为placeholder,那我们应该怎么解决IB自动添加ambiguity的constraint的问题呢?
事实上,如果我们的view是个custom view的话,IB是不会自动添加ambity的constraint,IB会添加NSContentSizeLayoutConstraint,然后在runtime时调用custom view 的intrinsicContentSize来知道view应该有多大。
还有继续说说intrinsicContentSize,事实上它不是一个人在战斗,其他的constraint只有一个priority,但是intrinsicContentSize有两个,分别叫Content Hugging Priority和Content Compression Resistancy Priority。 Content Hugging Priority控制当view frame>intrinsicContentSize时,是不是要缩小view 的size;而Content Compression Resistancy Priority控制当view frame
@end
如果是整数的属性标签信息是不够的,我们还可以得到更多新奇的东西,为视图类增加我们自己命名的属性,然后可以打印到错误消息中。我们甚至可以在Interface Builder中,使用identity inspector中的 “User Defined Runtime Attributes”为自定义属性分配值。
@interface UIView (AutoLayoutDebugging)
- (void)setAbc_NameTag:(NSString *)nameTag;
- (NSString *)abc_nameTag;
@end
@implementation UIView (AutoLayoutDebugging)
- (void)setAbc_NameTag:(NSString *)nameTag
{
objc_setAssociatedObject(self, “abc_nameTag”, nameTag, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@implementation NSLayoutConstraint (AutoLayoutDebugging)
@end
通过这种方法错误消息变得更可读,并且你不需要找出内存地址对应的视图。然而,对你而言,你需要做一些额外的工作以确保每次为视图分配的名字都是有意义。
由于这个方法是私有的,确保正式产品里面不要包含这个方法调用的任何代码。为了防止你犯这种错误,你可以在视图的category中这样做:
@implementation UIView (AutoLayoutDebugging)
- (void)printAutoLayoutTrace {
#ifdef DEBUG
NSLog(@”%@”, [self performSelector:@selector(_autolayoutTrace)]);
#endif
}
@end
_autolayoutTrace打印的结果如下:
正如不可满足约束条件的错误消息一样,我们仍然需要弄明白打印出的内存地址所对应的视图。
另一个标识出有歧义布局更直观的方法就是使用exerciseAmbiguityInLayout。这将会在有效值之间随机改变视图的frame。然而,每次调用这个方法只会改变frame一次。所以当你启动程序的时候,你根本不会看到改变。创建一个遍历所有视图层级的辅助方法是一个不错的主意,并且让所有的视图都有一个歧义的布局“jiggle”。
@implementation UIView (AutoLayoutDebugging)
- (void)exerciseAmiguityInLayoutRepeatedly:(BOOL)recursive {
#ifdef DEBUG
if (self.hasAmbiguousLayout) {
[NSTimer scheduledTimerWithTimeInterval:.5
target:self
selector:@selector(exerciseAmbiguityInLayout)
userInfo:nil
repeats:YES];
}
if (recursive) {
for (UIView *subview in self.subviews) {
[subview exerciseAmbiguityInLayoutRepeatedly:YES];
}
}
#endif
} @end
NSUserDefault选项
有几个有用的NSUserDefault选项可以帮助我们调试、测试自动布局。你可以在代码中设定,或者你也可以在scheme editor中指定它们作为启动参数。 顾名思义,UIViewShowAlignmentRects和NSViewShowAlignmentRects设置视图可见的alignment rects。NSDoubleLocalizedStrings简单的获取并复制每个本地化的字符串。这是一个测试更长语言布局的好方法。(谷了一张图告诉你什么是NSDoubleLocalizedStrings):
最后,设置AppleTextDirection和NSForceRightToLeftWritingDirection为YES,来模拟从右到左的语言。
约束条件代码
当在代码中设置视图和他们的约束条件时候,一定要记得将translatesAutoResizingMaskIntoConstraints设置为NO。如果忘记设置这个属性几乎肯定会导致不可满足的约束条件错误。即使你已经用自动布局一段时间了,但还是要小心这个问题,因为很容易在不经意间发生产生这个错误。
当你使用visual format language设置约束条件时,constraintsWithVisualFormat:options:metrics:views:方法有一个很有用的参数选择。如果你还没有用过,请参见文档。这不同于格式化字符串只能影响一个视图,它允许你调整在一定范围内的视图。举个例子,如果用可视格式语言指定水平布局,那么你可以使用NSLayoutFormatAlignAllTop排列可视语言里所有视图为上边缘对齐。
还有一个使用可视格式语言在父视图中居中子视图的小技巧,这技巧利用了不均等约束和可选参数。下面的代码在父视图中水平排列了一个视图:
UIView *superview = theSuperView;
NSDictionary *views = NSDictionaryOfVariableBindings(superview, subview);
NSArray *c = [NSLayoutConstraint constraintsWithVisualFormat:@”V:[superview]-(<=1)-[subview]”] options:NSLayoutFormatAlignAllCenterX metrics:nil views:views];
[superview addConstraints:c];
这利用了NSLayoutFormatAlignAllCenterX选项在父视图和子视图间创建了居中约束。格式化字符串本身只是一个虚拟的东西,它会产生一个指定的约束,通常情况下只要子视图是可见的,那么父视图底部和子视图顶部边缘之间的空间就应该小于等于1点。你可以颠倒示例中的方向达到垂直居中的效果。
使用可视格式语言另一个方便的辅助方法就是我们在上面例子中已经使用过的NSDictionaryFromVariableBindings宏指令,你传递一个可变数量的变量过去,返回得到一个键为变量名的字典。
为了布局任务,你需要一遍一遍的调试,你可以方便的创建自己的辅助方法。比如,水平排列视图时,你经常需要根据固定距离垂直的隔开一对相同类型的视图,用下面的方法将会方便很多:
@implementation UIView (AutoLayoutHelpers)
+ leftAlignAndVerticallySpaceOutViews:(NSArray *)views distance:(CGFloat)distance
{
for (NSUInteger i = 1; i < views.count; i++) {
UIView *firstView = views[i - 1];
UIView *secondView = views[i];
firstView.translatesAutoResizingMaskIntoConstraints = NO;
secondView.translatesAutoResizingMaskIntoConstraints = NO;
NSLayoutConstraint *c1 = constraintWithItem:firstView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:secondView attribute:NSLayoutAttributeTop multiplier:1 constant:distance];
NSLayoutConstraint *c2 = constraintWithItem:firstView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:secondView attribute:NSLayoutAttributeLeading multiplier:1 constant:0];
[firstView.superview addConstraints:@[c1, c2]];
}
}
@end
同时也有许多不同的自动布局帮助库采用了不同的方法来简化约束条件代码。
性能
自动布局是布局过程中额外的一个步骤。它需要一组约束条件,并把这些约束条件转换成frame。因此这自然会产生一些性能的影响。你需要知道的是,在绝大数情况下,如果你处理了非常关键的视图代码,那么它用来解决约束条件系统的时间是可以忽略不计的。
例如,有一个collection view,当新出现一行时,你需要在屏幕上呈现几个新的cell,并且每个cell包含几个基于自动布局的子视图,这时你需要注意你的性能了。幸运的是,我们不需要用直觉来感受上下滚动的性能。启动Instruments真实的测量一下自动布局消耗的时间。当心NSISEngine类的方法。
另一种情况就是当你一次显示大量视图时可能会有性能问题。将约束条件转换成视图的frame时,解释算法是超线性复杂的。这意味着当有一定数量的视图时,性能将会变得非常低下。确切的数目取决于你具体使用情况和视图配置。但是,给你一个粗略的概念,在当前iOS设备下,这个数字大概是100。你可以读这两个博客了解更多的细节(1,2)。
记住,这些都是极端的情况,不要过早的优化,并且避免自动布局潜在的性能影响。这样大多数情况便不会有问题。但是如果你怀疑这花费了你完全流畅地加载用户界面的时间,分析你的代码,然后你再去考虑用回手动设置frame有没有意义。此外,硬件将会变得越来越能干,并且Apple也会继续调整自动布局的性能。所以现实世界中极端情况的性能问题也将随着时间减少。