代码之家  ›  专栏  ›  技术社区  ›  Drux

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

  •  14
  • Drux  · 技术社区  · 11 年前

    我现在很难理解为什么下面的单元测试在iPad2上失败了。自动布局似乎有点错位(0.5分) view 在…内 superview 相对于两个布局约束所要求的精确居中。特别奇怪的是,关键的测试(但最后一个断言)在iPhone 5上通过,因此明显的舍入误差只影响一个(iOS 6)平台。这是怎么回事?

    更新1 我修改了代码,以确保两个框架在宽度和高度方面都受到足够的约束,即使 translatesAutoresizingMaskIntoConstraints NO ,作为一种可能相关的补救措施 here 然而,这显然并不能改变现状。

    #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
    

    更新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?
    }
    
    1 回复  |  直到 8 年前
        1
  •  15
  •   user1059264 user1059264    11 年前

    您所展示的是,自动布局讨厌错位的视图。在非视网膜设备上,距离最近 像素 最近的吗 指向 ,所以它四舍五入到整数。在视网膜屏幕上最接近 像素 最近的吗 中点 ,所以它四舍五入。您可以通过将第二次测试中的y更改为950.25来证明这一点,并注意view.frame保留为{{0,950.5},{768,8}}(而不是更改为{0、950.25}、{768、8})。

    (只是为了证明它是四舍五入的,而不是 ceil ing,如果将y更改为950.2视图。帧变为{{0,950},{768,8}}。)