I have an annoying problem with auto layout that occurs in iOS 8, while the same setup works fine in iOS 7.
I've simplified and isolated the problematic situation, and here is how it should work - and how it does work in iOS 7:
The feature consists of one container view (blue), and three sub views:
I've added a slider that controls the width of the container view by amending the width constraint of the container.
The auto layout constraints anchors the label to the left-hand-side, the button to the right-hand-side, and the image view is anchored to both sides without having a width constraint.
In iOS 8, the exact same setup looks like this:
Notice how the image view (the black line) appears to be anchored with an offset outside to the right of the container view.
I've tried everything and I can't figure out why this behaves differently in iOS 8. I've removed and replaced all the constraints multiple times but I still get the same result.
These are the constraints set on each of the sub views, respectivly:
And these are the constraints for the container view:
The width constraint of the container view is manipulated by the slider like this:
- (IBAction)handleSliderChange:(id)sender
{
self.buttonWidth.constant = self.slider.value * 1024.0;
[self.containerView setNeedsLayout];
[self.containerView setNeedsUpdateConstraints];
[self.containerView layoutIfNeeded];
[self.view setNeedsLayout];
[self.view setNeedsUpdateConstraints];
[self.view layoutIfNeeded];
}
Others that have had problems with auto layout in iOS 8 seems to have been able to solve similar issues by calling setNeedsLayout
and / or setNeedsUpdateConstraints
directly on the containing view, which is why I'm being very explicit in calling it on both the containing view and the root view. I've tried all combinations of the above, but nothing seems to make a difference.
I'm starting to lose hope of finding a solution, but perhaps someone here has dealt with a similar situation and could bring some clarity into this issue?
Edit: I should add that I've also tried to set constraints from the image view to the label and button as opposed to the edges of the containing view with the same result.
Edit 2: Here's the test project that I've been using when trying to isolate the issue: Download
It's not a problem of Auto Layout, but must be a bug in iOS 8 about handling sliced image assets.
With your sample project:
UIImage *img = [UIImage imageNamed:@"pointer-dark-left"];
NSLog(@"alignmentRect: %@", NSStringFromUIEdgeInsets(img.alignmentRectInsets));
NSLog(@"capInsets: %@", NSStringFromUIEdgeInsets(img.capInsets));
This code prints
// iOS 7
alignmentRect: {0, 0, 0, 0}
capInsets: {0, 5.5, 0, 134}
// iOS 8
alignmentRect: {0, 0, 0, 65.5}
capInsets: {0, 5.5, 0, 134}
alignmentRectInsets.right
is 65.5
!
As documented, Autolayout works with alignment rectangles, and UIImageView
uses its images alignmentRectInsets
. So, your self.line
has 65.5
pt extra right width.
Of course, you didn't set the Alignment Insets in xcassets
editor, I believe it's a bug.
Here is a ugly, but working, workaround :(
- (void)viewDidLoad {
[super viewDidLoad];
self.line.image = [self.line.image imageWithAlignmentRectInsets:UIEdgeInsetsZero];
}
I build up an app with same components and it is working all fine. Maybe you can clear all constraints and setup again.
#import "ViewController.h"
@interface ViewController () {
NSLayoutConstraint* csViewWidth;
}
@end
@implementation ViewController
- (void)viewDidLoad {
NSDictionary *views = @{
@"label": self.uiLabel,
@"image": self.uiImage,
@"button": self.uiButton
};
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[label]-[image]-[button]-|"
options:0
metrics:nil
views:views];
[self.view addConstraints:constraints];
NSArray *panelConstraint = [NSLayoutConstraint constraintsWithVisualFormat:@"H:[panel(320)]" options:0 metrics:nil views:@{ @"panel": self.uiPanel }];
csViewWidth = [panelConstraint firstObject];
[self.view addConstraints:@[csViewWidth]];
}
- (IBAction)onSliderChanged:(UISlider*)sender {
float containerWidth = self.view.frame.size.width;
csViewWidth.constant = sender.value * containerWidth;
}
@end
This solution is tested and working fine. There is no need to call setNeedsLayout and layoutIfNeeded.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With