12

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:

Constraints working in iOS 7

The feature consists of one container view (blue), and three sub views:

  • A label
  • An image view (with a sliced, scaled image)
  • A button

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:

Constraints broken in iOS 8

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:

Sub views constraints

And these are the constraints for the container view:

Container view constraints

The width constraint of the container view is manipulated by the slider like this:

Width constraint connection

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

david.emilsson
  • 2,313
  • 1
  • 20
  • 24
  • Could this be because you have accidentally pinned the constraint to the superview's _margin_? This is a new feature of iOS 8 and is likely to trip people up. – matt Oct 09 '14 at 21:20
  • @matt Interesting! Hadn't heard of that. How would I be able to tell whether or not I am pinned to the margin? This is a screenshot detailing the types of constraints on the image view: http://i.imgur.com/N1jeHY6.png – david.emilsson Oct 09 '14 at 21:28
  • 1
    Okay but hang on - those constraints in that screen shot are for just one size class. So now I'm wondering whether the constraints you're looking at are really the constraints that are in effect. Do you know about size classes and conditional constraints? – matt Oct 09 '14 at 21:48
  • I've only read at a glance about size classes etc. I'm working on an iPad-only app at the moment, so I haven't had to dig deeper into it. If I switch to the "All" tab instead of "This Size Class" [I get the same constraints...](http://i.imgur.com/JkBNquY.png) Is there anywhere else I should check? – david.emilsson Oct 09 '14 at 21:59
  • I downloaded your test project and it behaves the same in iOS 7 and iOS 8. That's not what you said would happen. – matt Oct 09 '14 at 23:12
  • 1
    The way it behaves in iOS 8 (and iOS 7) is just like your _first_ gif - the one that you say is how it should work. Thus I cannot reproduce any problem - it is behaving the way you say it should behave. – matt Oct 09 '14 at 23:15
  • Maybe your project's cache is corrupted. I suggest you do this: http://stackoverflow.com/a/6247073/341994 – matt Oct 09 '14 at 23:17
  • Now I'm intrigued! I have it consistently working as intended in iOS 7 and consistently failing in iOS 8. Even after cleaning the build and following the instructions on your link. This is a good thing, it could mean that the issue is somewhere, somehow in my XCode configs. – david.emilsson Oct 09 '14 at 23:25
  • 1
    I didn't do anything special - all I did was fix the deployment target so I could test on both iOS 7 and iOS 8. But I needn't have done even that. When I run on the iOS 8 iPad simulator in landscape it looks just like your first gif. – matt Oct 10 '14 at 00:12
  • 1
    The problem can be reproduced with Xcode 6.0.1(6A317)/iOS8.0(12A365) Simulator, but not with Xcode 6.1(6A1046a)/iOS8.1(12B407) Simulator. I believe the bug was fixed in iOS8.1. – rintaro Oct 10 '14 at 12:39
  • On a side note: How did you capture/make the gif? – Black Frog Oct 10 '14 at 16:51
  • @BlackFrog - I used an application called GifGrabber ([link](http://www.gifgrabber.com/)). It's not great, and there's other similar applications out there, but it does the trick. – david.emilsson Oct 11 '14 at 00:10

3 Answers3

7

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.5pt extra right width.

Of course, you didn't set the Alignment Insets in xcassets editor, I believe it's a bug.

screenshot

Here is a ugly, but working, workaround :(

- (void)viewDidLoad {
    [super viewDidLoad];
    self.line.image = [self.line.image imageWithAlignmentRectInsets:UIEdgeInsetsZero];
}
rintaro
  • 51,423
  • 14
  • 131
  • 139
  • Right on the money! I managed to get it to work in this case by adjusting the slices in the `xcassets` editor to not have a "right side". However as you say, it must be a bug in how it handles it. I will take a closer look at your answer to see it I can figure out a more permanent way to go about this. – david.emilsson Oct 10 '14 at 12:53
  • That error is not caused by a bug. See my answer about slicing. – Tuan Oct 10 '14 at 15:40
1

I think you connected your constraints wrong.

I build up an app with same components and it is working all fine. Maybe you can clear all constraints and setup again.

I add code to do it programmatically.

#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.

Community
  • 1
  • 1
Tuan
  • 893
  • 1
  • 9
  • 24
  • Interesting. I've tried connecting the line to the button and the label with the same result (see edit to the original question). I'll experiment with the visual format to see if I can get it working that way. – david.emilsson Oct 09 '14 at 21:44
  • 1
    The line should have hugging priority default(250) because you want it to expand.. try to set hugging prio of button and label higher. as long as your line keeps aligning to superview i think your set up constraints wrong. be sure not to have constraints to multiple objects an right side. – Tuan Oct 09 '14 at 21:49
  • Hmm... Changing the hugging priority doesn't seem to have an effect unfortunately. Nor does changing the "Content Compression Resistance Priority" factor. I'm sure there's an obvious error in there somewhere due to lack of understanding how auto layout works and how its functionality has changed in iOS 8, but I've tried to the best of my ability to ensure that only the _absolutely necessary_ constraints are added... – david.emilsson Oct 09 '14 at 22:11
  • 1
    This solution is definitely working. I edited for more Details. – Tuan Oct 10 '14 at 09:36
  • Thank you so much for the help, but as rintaro pointed out, the underlying problem is a bug with sliced images in iOS 8... You really helped me to confirm that my constraints were set up right which was very valuable in tracking down the problem! – david.emilsson Oct 10 '14 at 12:59
0

That is not because of an iOS bug

The image is sliced wrong

I tested with your project and it works now as expected.

The slicing has to look like this:

O|-|-----------|

  • First: behind the circle
  • Second: 1 unit after first
  • Third: at very end

With your slicing you tell ios to take a long tail which is breaking your layout.

Community
  • 1
  • 1
Tuan
  • 893
  • 1
  • 9
  • 24
  • I agree that the original slicing is not so good, but it's not **too** long. The minimum width is `(left:11px + right:268px) / 2` = `139.5pt`. – rintaro Oct 10 '14 at 16:31
  • It is sliced wrong which is causing the described error. – Tuan Oct 10 '14 at 18:50
  • You are correct that the rightmost slice should be towards the right end of the image, however it should only manifest itself as a problem when the image view is narrower than the original width of the sliced image. (In which case the left and right parts of the image would scale disproportionately in order to fit.) In this case the right slice of the image is placed _outside_ of the right hand side of the image view which simply must be a bug. This doesn't happen in iOS 7 and apparently not in iOS 8.1 either. – david.emilsson Oct 11 '14 at 00:07