ios auto layout demystified (一)

Ambiguous Layout 

在开发过程中,你可以通过调用hasAmbiguousLayout 来测试你的view约束是否足够的。这个会返回boolean值。如果有一个不同的frame就会返回yes,如果view的约束完全指定了就会返回no。

这些结果是view指定的。例如,一个设定了完全约束的view他的子view有ambiguous layout但是此view却没有那么你就应该为每一个view单独测试layout是否存在ambiguouslayout。

Intrinsic Content Size 

使用auto layout,view的content扮演着非常重要的角色。每个view的intrinsicContentSize描述了不会剪切data的显示完整view content的最小空间。

例如一个image view,content size根据image显示的size设置。一个大的image需要一个大的固有的content size。想像一下的code。他将一个ios7标准的icon.png image来加载到一个image view中去并且报告view的固有content size。如你所想像的,size是60乘60 points。image的大小提供给了view。

UIImageView *iv = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"Icon-60.png"]];

NSLog(@"%@", NSStringFromCGSize(iv.intrinsicContentSize)); 

对于button,固有的content size根据他的title而有不同。随着title grows或者shrinks,button的固有的content size也会调节来做适应。这个代码片段创建了一个button并且分配给他几个不同的title。在每个分配后报告了固有content size。

UIButton *button =
[UIButton buttonWithType:UIButtonTypeSystem];

// Longer title, Figure 1-7, middle image
[button setTitle:@"Hello World" forState:UIControlStateNormal]; NSLog(@"%@: %@", [button titleForState:UIControlStateNormal],

NSStringFromCGSize(button.intrinsicContentSize));

// Shorter title, Figure 1-7, bottom image
[button setTitle:@"On" forState:UIControlStateNormal]; NSLog(@"%@: %@", [button titleForState:UIControlStateNormal],

NSStringFromCGSize(button.intrinsicContentSize));

When run, this snippet outputs the following sizes:

2013-07-02 12:16:46.576 HelloWorld[69749:a0b] Hello World: {78, 30} 2013-07-02 12:16:46.577 HelloWorld[69749:a0b] On: {30, 30} 

 
hello world版本展示了一个更宽的固有content size,两者都用了同样的高度。这些值可以根据你自定义的font size和title text而有变化。
view的固有size允许auto layout最好得匹配view得frame来适应原始得content。当一个view有一个固有的content size的时候,size占据两个属性中的一个。你可以,例如,将一个text-based控件或者一个image view放置到他的superview中。而且他的layout不会产生ambiguous。这个固有的content size加上location combine来实现完整的指定placement。当你改变一个view的固有contents时,你需要调用invalidateIntrinsicContentSize 来使得auto layout知道宠溺计算下一个layout。

Compression Resistance and Content Hugging 

同样的建议,compression resistance压缩阻力涉及一个view保护他的content的方式。一个有高compression resistance的view会防止shrinking,缩小。他不会允许content被裁剪。看以下的例子,约束想设置button width是40 points。

Compression resistance 压缩阻力描述了一个view如何尝试保存他的最小固有content size。图中上面的button有一个更高的压缩阻力值,下面的有一个low value。正如你看到的,高优先级确保上面的button保存他的固有content。下面的button阻力太低。resizing成功,bottom button压缩了并且剪切了text文本。bottom button的“don't clip”请求(就是compression resistance 优先级)仍然在那里。但是并没有重要到阻止将宽度设置为40的约束。auto layout经常遇到两个冲突的请求。当只有一个请求会成功时,他就会满足高优先级的那个。

你可以通过View > Utilities > Show Size Inspector > View > Content Compression Resistance 来指定view的压缩阻力。分别设置水平和垂直方向的。value从1(最低)到1,000(请求的优先级)不等。默认的是750.

[button setContentCompressionResistancePriority:500 forAxis:UILayoutConstraintAxisHorizontal]; 

view的content hugging priority 。这个涉及view来防止额外的padding在他的content周围出现或者stretching他的content。下图中的buttons被告知stretch。上面的button有一个高的content hugging priority。下面的button有一个低值。button pads他的content并且产生了变宽的效果。Content hugging defaults to 250. 

[button setContentHuggingPriority:501 forAxis:UILayoutConstraintAxisHorizontal] 

content hugging描述view匹配他的frame到他的content size的期望。一个strong hugging priority限制view增长过大超过他的现有的content。一个weak优先级可以允许viewstretch并且在一堆padding中隔离他的content。

Image Embellishments 

当你在你的pictures中包含装饰的时候比如shadows,sparkles,badges和其他的items来延伸到images的content外时,image的原始size可能不会再反应你想让auto layout来控制的方式。在auto layout中,约束决定view的size和placement。使用一个

几何图形元素叫做alignment rectangle对齐矩形。uikit api调用可以帮助你控制那个placement。布置

Alignment Rectangles 

随着程序员创建复杂的views,他们可以使用比如shadows,外部高亮,exterior highlights,雕刻线engraving lines,反光reflections。就像他们做的,这些特征经常被应用到绘制image中。不想frames,view的对齐矩形应当限制在一个core visual element中。他的大小应当保留unaf- fected就像new items绘制到view上。看下图,view绘制有shadow和badge标记。当布局这个view时,你想让auto layout 只关注core element-蓝色矩形的校准。而不是装饰物的校准。

试图的对齐矩形(center)严格涉及要被对齐的core visual element而不是装饰品。

中间的image高亮view的对齐矩形。这个矩形排除了所有的装饰物比如drop shadow和标记。

这个就是你想让auto layout考虑的部分。形成对比的contrast时右侧image显示的矩形。

这个版本包括所有的装饰品,扩展到了应当考虑对齐的view frame的外部。这些装饰品可能潜在抛弃view的对齐特征,例如他的中间,底部和右侧如果他们在layout时被考虑到。

使用alignment rectangles而不是frames,auto layout确保主要information像view的edges和center都会在layout中被考虑到。

在下图中,被装饰的view和背景网格完美的对齐。他的标记和shadow在摆放过程中却是不被考虑的。

auto layout仅仅会考虑view的对齐矩形当在他的superview中居中布局时。shadow和badge不会作用于他的placement。

Visualizing Alignment Rectangles 

ios和os x可以让你在应用程序中使用对齐矩形来叠加views。你从app的scheme中设置一个简单的加载参数:ios中UIViewShowAlignmentRects 和 os x中的NSViewShowAlignmentRects 。设置参数为yes并且使用dash破折号来作为前缀。当app运行时,矩形在每个view上显示。

 

Alignment Insets 

绘制艺术总是包含hard-coded部分比如高亮,shadows或者其他的。下图展示了当同auto layout一起使用image-based装饰品时候出现的典型的问题。左图展示了一个基本的image view,效果通过photoshop创建的。我使用了一个标准的drop shadow effect。

当添加到image view时,20乘20的区域脱离了view的对齐矩形。导致他有点儿太靠左上。在默认的执行中,image view并不清楚image包含装饰元素。你需要告知如何调整他的实质content这样对齐矩形就会只考虑core materials。

为了容纳shadow,你加载并且重新创建image。这分为两步。

首先,像平常一样加载图片。

然后,为image调用imageWithAlignmentRectInsets来产生新的版本来执行特定的插图。下图展示了一个20像素的shadow通过将对齐矩形嵌入到右下方。 

  

 

左图,image view在没有调整的情况下创建。离左上方太远了。而右面的image view正好居中。insets定义了距离上,左,下,右的矩形区域的距离。你使用这种方式来描述移动他们多远或者使用负值来使得他们移出矩形区域。这些insets保证了对齐矩形是正确的。

The fields are defined as follows:

typedef struct {
CGFloat top, left, bottom, right;

} UIEdgeInsets;

 

指定了对齐矩形insets后可以看到改善的图。

HelloWorld[53122:c07] Frame: {{70, 162}, {200, 200}} HelloWorld[53122:c07] Intrinsic Content Size: {180, 180} HelloWorld[53122:c07] Alignment Rect: {{70, 162}, {180, 180}} 

 手动设置这些insets是非常痛苦的,特别是如果你稍后可能会更新你的涂层。当你知道对齐矩形和重叠的image bounds时,你就可以自动计算你所需要的edge insets来传递给这个方法。listing 1-1定义了一个简单的inset builder。他确定对齐矩形距离每个parent 矩形edge的距离。返回一个UIEdgeInset结构来表示这些值。使用这个函数来创建你的core visuals insets。

UIEdgeInsets BuildInsets(
CGRect alignmentRect, CGRect imageBounds)

{
// Ensure alignment rect is fully within source CGRect targetRect =

CGRectIntersection(alignmentRect, imageBounds);

// Calculate insets
UIEdgeInsets insets;
insets.left = CGRectGetMinX(targetRect) –

CGRectGetMinX(imageBounds); insets.right = CGRectGetMaxX(imageBounds) –

CGRectGetMaxX(targetRect); insets.top = CGRectGetMinY(targetRect) –

CGRectGetMinY(imageBounds); insets.bottom = CGRectGetMaxY(imageBounds) –

CGRectGetMaxY(targetRect);

return insets; } 

Declaring Alignment Rectangles 

cocoa和cocoa touch提供几个额外的方式来汇报对齐几何学。你可以执行alignmentRectForFrame:, frameForAlignmentRect:, baselineOffsetFromBottom, and alignmentRectInsets. 这些方法允许你的views声明并且从code中转化对齐矩形。

你可以忽略对齐矩形和insets。

A few notes on these items:

■ alignmentRectForFrame: and frameForAlignmentRect: must always be mathematical inverses of each other. 

  • Most custom views only need to override alignmentRectInsets to report content location within their frame.

  • baselineOffsetFromBottom is available only for NSView and refers to the distance between the bottom of a view’s alignment rectangle and the view’s content baseline, such as that used for laying out text. This is important when you want to align views to text baselines and not to the lowest point reached by typographic descenders, like j and q. 

Implementing Alignment Rectangles 

listing 1-2提供了一个例子基于代码的对齐几何校正。这个os x app创建了一个fixed-size view并且绘制了一个shadowed rounded 举行区域在其中。当USE_ALIGNMENT_ RECTS设置为1时,他的alignmentRectForFrame和 frameForAlignmentRect:方法从frames和对齐矩形转化。1-15展示的这些方法允许view根据适合的对齐来显示。

@interface CustomView : NSView

@end

@implementation CustomView
- (void) drawRect:(NSRect)dirtyRect {

NSBezierPath *path; 

// Calculate offset from frame for 170x170 art CGFloat dx = (self.frame.size.width - 170) / 2.0f; CGFloat dy = (self.frame.size.height - 170);

// Draw a shadow
NSRect rect = NSMakeRect(8 + dx, -8 + dy, 160, 160); path = [NSBezierPath

bezierPathWithRoundedRect:rect xRadius:32 yRadius:32]; [[[NSColor blackColor] colorWithAlphaComponent:0.3f] set]; [path fill];

// Draw fixed-size shape with outline rect.origin = CGPointMake(dx, dy); path = [NSBezierPath

bezierPathWithRoundedRect:rect xRadius:32 yRadius:32]; [[NSColor blackColor] set];
path.lineWidth = 6;
[path stroke];

[ORANGE_COLOR set];

[path fill]; }

- (NSSize)intrinsicContentSize {

// Fixed content size - base + frame

return NSMakeSize(170, 170); }

#define USE_ALIGNMENT_RECTS 1 #if USE_ALIGNMENT_RECTS

  • -  (NSRect)frameForAlignmentRect:(NSRect)alignmentRect {

    // 1 + 10 / 160 = 1.0625
    NSRect rect = (NSRect){.origin = alignmentRect.origin}; rect.size.width = alignmentRect.size.width * 1.06250; rect.size.height = alignmentRect.size.height * 1.06250; return rect;

    }

  • -  (NSRect)alignmentRectForFrame:(NSRect)frame {

    // Account for vertical flippage
    CGFloat dy = (frame.size.height – 170.0) / 2.0;
    rect.origin = CGPointMake(frame.origin.x, frame.origin.y + dy);

    rect.size.width = frame.size.width * (160.0 / 170.0); rect.size.height = frame.size.height * (160.0 / 170.0); 

return rect; }

#endif @end 

Figure 1-15 Implementing intrinsic content size and frame/alignment rect conversion methods ensures that your view will align and display correctly (as shown on the left) rather than be mis- aligned and possibly clipped (as shown on the right). 

 

posted @ 2015-02-27 15:24  如来藏  阅读(...)  评论(...编辑  收藏