为什么iOS的自动布局导致对pre-Retina显示屏明显的舍入误差(包括单元测试) [英] Why does iOS auto layout lead to apparent rounding errors on pre-Retina displays (unit test included)

查看:101
本文介绍了为什么iOS的自动布局导致对pre-Retina显示屏明显的舍入误差(包括单元测试)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我目前有一个很难理解为什么在iPad 2自动布局以下单元测试失败似乎略(0.5分)错位置视图上海华相对的需要的两个布局约束的准确定心。看起来特别奇怪的是,在关键的考验(不过,最后断言)通过在iPhone 5,所以明显的舍入误差只影响一个(iOS 6中)的平台。这是怎么回事呢?

更新1 我已经改变了code,以确保这两个框架在宽度和高度方面足够的约束,即使 translatesAutoresizingMaskIntoConstraints NO ,建议作为一个可能与补救这里 。然而,这显然是不改变的情况

 #进口BugTests.h@implementation BugTests - (无效){testCenteredLayout
    的UIView *上海华= [[UIView的的alloc] initWithFrame:方法CGRectMake(0,0,768,88)];
    superview.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin;
    superview.translatesAutoresizingMaskIntoConstraints = YES;    的UILabel *视图= [[的UILabel的alloc] initWithFrame:方法CGRectMake(0,0,0,0)];
    view.text = @对iPad的单轮。
    view.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin;
    view.translatesAutoresizingMaskIntoConstraints = NO;
    [查看addConstraint:[NSLayoutConstraint constraintWithItem:查看属性:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:无属性:NSLayoutAttributeNotAnAttribute乘数:0.0常数:206.0]];
    [查看addConstraint:[NSLayoutConstraint constraintWithItem:查看属性:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:无属性:NSLayoutAttributeNotAnAttribute乘数:0.0不变:21.0]];    [上海华addSubview:视图]    [上海华addConstraint:[NSLayoutConstraint constraintWithItem:上海华属性:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:查看属性:NSLayoutAttributeCenterX乘数:1.0常数:0.0];
    [上海华addConstraint:[NSLayoutConstraint constraintWithItem:上海华属性:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:查看属性:NSLayoutAttributeCenterY乘数:1.0常数:0.0];    STAssertEquals(superview.center,CGPointMake(384,44),无); //成功
    STAssertEquals(view.center,CGPointMake(0,0),无); //成功    [上海华setNeedsLayout]
    [上海华layoutIfNeeded]    STAssertTrue(superview.hasAmbiguousLayout,零!);    STAssertEquals(superview.frame.size,CGSizeMake(768,88),无); //成功
    STAssertEquals(view.frame.size,CGSizeMake(206,21),无); //成功    STAssertEquals(superview.center,CGPointMake(384,44),无); //成功    STAssertEquals(superview.center,view.center,无); //失败:为什么?
    STAssertEquals(view.center,CGPointMake(384,44.5),无); //成功:为什么呢?
}@结束

更新2 我隔离(显然)同样的问题的另一个实例在第二单元测试。这一次,它涉及到一个顶部(未中心)的约束,而这一次的分数点坐标似乎是触发。 (该测试也成功上如使用pre-视网膜设备 Y = 951 ,即奇点坐标)。我在各种模拟器配置,选中(旁边我的身体的iPad 2和iPhone 5)occurence确实似乎绑缺乏Retina显示屏的。 (再次感谢@ArkadiuszHolko领先。)

我现在的这些测试的感觉是,人们必须避免,如果需要在pre-Retina显示屏的点精确的自动布局奇高度和分数y坐标。但是,为什么?

   - (无效){testNonRetinaAutoLayoutProblem2
    的UIView *上海华= [[UIView的的alloc] initWithFrame:方法CGRectMake(0,0,768,1004)];
    superview.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin;
    superview.translatesAutoresizingMaskIntoConstraints = YES;    CGFloat的Y = 950.5; //例如见pageControlTopConstraint    的UIView *视图= [[UIView的的alloc] initWithFrame:方法CGRectMake(0,0,0,0)];
    view.translatesAutoresizingMaskIntoConstraints = NO;
    [上海华addConstraint:[NSLayoutConstraint constraintWithItem:查看属性:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:上海华属性:NSLayoutAttributeLeading乘数:1.0常数:0.0];
    [上海华addConstraint:[NSLayoutConstraint constraintWithItem:查看属性:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:上海华属性:NSLayoutAttributeTrailing乘数:1.0常数:0.0];
    [上海华addConstraint:[NSLayoutConstraint constraintWithItem:查看属性:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:上海华属性:NSLayoutAttributeTop乘数:1.0不变:Y]];
    [上海华addConstraint:[NSLayoutConstraint constraintWithItem:查看属性:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:无属性:NSLayoutAttributeNotAnAttribute乘数:0.0不变:8];    [上海华addSubview:视图]    [上海华setNeedsLayout]
    [上海华layoutIfNeeded]    STAssertTrue(superview.hasAmbiguousLayout,零!);
    STAssertTrue(view.hasAmbiguousLayout,零!);    STAssertEquals(superview.frame,CGRectMake(0,0,768,1004),无); //成功
    STAssertEquals(view.frame,CGRectMake(0,Y,768,8),无); //失败:为什么?
    STAssertEquals(view.frame,CGRectMake(0,Y + 0.5,768,8),无); //成功:为什么呢?
}


解决方案

您已经证明什么是自动布局厌恶对齐意见。在非Retina设备最接近的像素的是最近的,因此四舍五入到整数。在视网膜筛选最接近的像素的是最近的点半,因此四舍五入为最接近0.5。您可以通过在你的第二个测试改变y以950.25并指出,view.frame遗骸证明这一点{{0,950.5},{768,8}}(而不是更改为{{0,950.25},{768,8} })。

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

I'm currently having a hard time understanding why the following unit test fails on an iPad 2. Auto layout seems to slightly (by 0.5 points) mis-position view inside superview relative to the exact centering that's required by two layout constraints. What seems especially strange is that the crucial test (but-last assertion) passes on an iPhone 5, so the apparent rounding error affects only one (iOS 6) platform. What's going on here?

UPDATE 1 I've changed the code to ensure that that both frames are sufficiently constrained in terms of widths and heights even if translatesAutoresizingMaskIntoConstraints is NO, as suggested as a possibly related remedy here. However, this apparently does not change the situation.

#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

UPDATE 2 I've isolated another instance of (apparently) the same problem in a second unit test. This time it involves a top (not center) constraint, and this time a fractional point coordinate seems to be the trigger. (The test succeeds also on pre-Retina devices e.g. with y = 951, i.e. an odd point coordinate.) I've checked in various simulator configurations (next to my physical iPad 2 and iPhone 5) occurence indeed seems tied to the absence of a Ratina display. (Again, thanks to @ArkadiuszHolko for the lead.)

My current sense from these tests is that one has to avoid odd heights and fractional y-coordinates if point-exact auto layout on pre-Retina displays is required. But why?

- (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?
}

解决方案

What you've shown is that autolayout abhors misaligned views. On non-retina devices the closest pixel is the nearest point, so it rounds to whole numbers. On retina screens the closest pixel is the nearest half point, so it rounds to the nearest .5. You can demonstrate this by changing y in your second test to 950.25 and noting that view.frame remains {{0, 950.5}, {768, 8}} (instead of changing to {{0, 950.25}, {768, 8}}).

(Just to prove that it is rounding and not ceiling, if you change y to 950.2 view.frame becomes {{0, 950}, {768, 8}}.)

这篇关于为什么iOS的自动布局导致对pre-Retina显示屏明显的舍入误差(包括单元测试)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆