为什么iOS自动布局导致Retina前显示屏出现明显的舍入错误(包括单元测试)

Dru*_*rux 13 cocoa-touch ios retina-display autolayout

我目前很难理解为什么以下单元测试在iPad 2上失败.相对于两个布局约束所需的精确居中,自动布局似乎略微(0.5点)view内部误定位superview.看起来特别奇怪的是,关键测试(但最后断言)传递给iPhone 5,因此明显的舍入错误仅影响一个(iOS 6)平台.这里发生了什么?

更新1我已经改变了代码,以确保这两个帧的宽度和高度方面得到充分的约束,即使translatesAutoresizingMaskIntoConstraintsNO,所建议的可能相关的补救措施在这里.但是,这显然不会改变这种情况.

#import "BugTests.h"

@implementation BugTests

- (void)testCenteredLayout {
    UIView *superview = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 768, 88)];
    superview.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin;
    superview.translatesAutoresizingMaskIntoConstraints = YES;

    UILabel *view = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 0, 0)];
    view.text = @"Single Round against iPad.";
    view.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin;
    view.translatesAutoresizingMaskIntoConstraints = NO;
    [view addConstraint:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeWidth  relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:0.0 constant:206.0]];
    [view addConstraint:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:0.0 constant: 21.0]];

    [superview addSubview:view];

    [superview addConstraint:[NSLayoutConstraint constraintWithItem:superview attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:view attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0]];
    [superview addConstraint:[NSLayoutConstraint constraintWithItem:superview attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:view attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]];

    STAssertEquals(superview.center, CGPointMake(384, 44), nil); // succeeds
    STAssertEquals(view.center,      CGPointMake(  0,  0), nil); // succeeds

    [superview setNeedsLayout];
    [superview layoutIfNeeded];

    STAssertTrue(!superview.hasAmbiguousLayout, nil);

    STAssertEquals(superview.frame.size, CGSizeMake(768, 88), nil); // succeeds
    STAssertEquals(view.frame.size,      CGSizeMake(206, 21), nil); // succeeds

    STAssertEquals(superview.center, CGPointMake(384, 44), nil); // succeeds

    STAssertEquals(superview.center, view.center,            nil); // fails: why?
    STAssertEquals(view.center,      CGPointMake(384, 44.5), nil); // succeeds: why?
}

@end
Run Code Online (Sandbox Code Playgroud)

更新2我在第二次单元测试中隔离了(显然)同一问题的另一个实例.这次它涉及顶部(非中心)约束,这次分数点坐标似乎是触发器.(该测试也在Retina之前的设备上成功,例如y = 951,有一个奇点坐标.)我已经检查了各种模拟器配置(在我的物理iPad 2和iPhone 5旁边)出现确实似乎与没有Ratina显示器有关.(再次感谢@ArkadiuszHolko的领导.)

我目前从这些测试中得知的是,如果需要在Retina前显示器上进行精确的自动布局,则必须避免奇数高度和分数y坐标.但为什么?

- (void)testNonRetinaAutoLayoutProblem2 {
    UIView *superview = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 768, 1004)];
    superview.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin;
    superview.translatesAutoresizingMaskIntoConstraints = YES;

    CGFloat y = 950.5; // see e.g. pageControlTopConstraint

    UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 0)];
    view.translatesAutoresizingMaskIntoConstraints = NO;
    [superview addConstraint:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeLeading  relatedBy:NSLayoutRelationEqual toItem:superview attribute:NSLayoutAttributeLeading        multiplier:1.0 constant:0.0]];
    [superview addConstraint:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:superview attribute:NSLayoutAttributeTrailing       multiplier:1.0 constant:0.0]];
    [superview addConstraint:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeTop      relatedBy:NSLayoutRelationEqual toItem:superview attribute:NSLayoutAttributeTop            multiplier:1.0 constant:y]];
    [superview addConstraint:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeHeight   relatedBy:NSLayoutRelationEqual toItem:nil       attribute:NSLayoutAttributeNotAnAttribute multiplier:0.0 constant:8]];

    [superview addSubview:view];

    [superview setNeedsLayout];
    [superview layoutIfNeeded];

    STAssertTrue(!superview.hasAmbiguousLayout, nil);
    STAssertTrue(!view.hasAmbiguousLayout,      nil);

    STAssertEquals(superview.frame, CGRectMake(0, 0,       768, 1004), nil); // succeeds
    STAssertEquals(view.frame,      CGRectMake(0, y,       768,    8), nil); // fails: why?
    STAssertEquals(view.frame,      CGRectMake(0, y + 0.5, 768,    8), nil); // succeeds: why?
}
Run Code Online (Sandbox Code Playgroud)

小智 12

您所展示的是自动布局会憎恶错位观点.在非视网膜设备上,最近的像素是最近的,因此它会舍入到整数.在视网膜屏幕上,最近的像素是最近的半点,因此它舍入到最接近的.5.您可以通过将第二个测试中的y更改为950.25来证明这一点,并注意到view.frame仍为{{0,950.5},{768,8}}(而不是更改为{{0,950.25},{768,8} }).

(只是为了证明它是四舍五入而不是ceil,如果你将y改为950.2 view.frame变为{{0,950},{768,8}}.)