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