12
votes

I'm building a complicated project where, among other things, I need to set a UIPageViewController as a childview of a main view. I'm using autolayout, and using constraints to order the various elements on the main view.

The problem is, that when I try to run the app, it crashes due to conflicting NSAutoresizingMaskLayoutConstraints.

2013-10-28 16:22:18.419 Red Event App[1658:60b] Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints) 
(
"<NSLayoutConstraint:0x145c1990 V:|-(20)-[UINavigationBar:0x145bf6b0]   (Names: '|':UIView:0x145bf620 )>",
"<NSLayoutConstraint:0x145bf510 V:[UINavigationBar:0x145bf6b0]-(0)-[UIView:0x145bef70]>",
"<NSLayoutConstraint:0x145d0550 UIView:0x145a8c10.top == UIView:0x145bf620.top>",
"<NSAutoresizingMaskLayoutConstraint:0x145b3a40 h=-&- v=-&- UIView:0x145a8c10.midY == UIView:0x145bef70.midY + 56.5>",
"<NSAutoresizingMaskLayoutConstraint:0x145b3a70 h=-&- v=-&- UIView:0x145a8c10.height == UIView:0x145bef70.height + 113>"
)

The usual cure for this is setting TranslatesAutoresizingMaskIntoConstraints to no.

However, when I do this the child views of the UIPageViewController start to ignore the bounds of the PageViewController, and end up (as far as I can see) with an origin of (0,0).

I've tried to fix the position by setting the frames by hand both when setting up the datasource (_itemViewControllers):

- (void)setupLeafs{
    _itemViewControllers = [[NSMutableArray alloc]init];
    for(NSString *pagename in _datasource){
        RWNode *page = [_xml getPage:pagename];
        UIViewController *viewController = [RWNavigationController getViewControllerFromDictionary:[page getDictionaryFromNode]];
        viewController.view.frame = CGRectMake(self.view.frame.origin.x,self.view.frame.origin.y,self.view.frame.size.width, self.view.frame.size.height);

       [_itemViewControllers addObject:viewController];
   }
}

and when getting the page

- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController {
    UIViewController *nextViewController = [self getPreviousLeaf:viewController];
    nextViewController.view.frame = CGRectMake(self.view.frame.origin.x,self.view.frame.origin.y,self.view.frame.size.width, self.view.frame.size.height);
    return nextViewController;
}

but neither have any effect.

I need the constraints for what I'm doing, so sticking to Masks is not an option. I think what I'm looking for is a way to put constraints on the UIPageViewController children after they've been added (by whatever process calls viewControllerBeforeViewController). But I'd really like to hear about any way that this problem can be solved.

Edit: I have found a hack to solve the problem. I'm not quite sure if what I'm listing here is the entire solution, but it is what I notice right now, after more than a month of tinkering with the code.

First, in the view controller that sets up the pageviewcontroller, I have the following two lines, after I've initialized the pageviewcontroller

UIView *pageView = self.pageViewController.view; 
pageView.frame = self.view.frame;

Not that I have set [self.view setTranslatesAutoresizingMaskIntoConstraints:NO]; in this view controller.

Secondly, on the child controllers I check whether the view is being used inside or outside of a pageviewcontroller. Only if the view is not being used in a pageviewcontroller is [self.view setTranslatesAutoresizingMaskIntoConstraints:NO]; set on the child view.

Now, this works (for me) at the moment. But I would really like a solution that is less hacky.

4
The crash is probably due to something else. In the event of conflicting constraints, it usually ignores one of them (and logs the fact). Can you paste the whole stack trace?Dheeraj Vepakomma
It does say (to give an example): "Will attempt to recover by breaking constraint <NSLayoutConstraint:0x15671b70 UIView:0x156cf3e0.bottom == UIView:0x1568a390.bottom> Break on objc_exception_throw to catch this in the debugger. The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful." But there are no relevant log messages beyond this. And it does crash after having given this message.Rubberduck
Could you describe what you are visually trying to achieve? That would be very useful in order to give any kind of advice :)Johannes Fahrenkrug
@Rubberduck did you have any "good" solution instead of check "embedding in pageViewController"? Problem still exists in 2018.gaussblurinc

4 Answers

7
votes

When using AutoLayout, you should never directly set the frame of a view. Constraints are used to do this for you. Normally if you want to set your own constraints in a view, you override the updateConstraints method of your UIViews. Make sure the content views for the page controller allow for their edges to be resized since they will be sized to fit the page view's frame. Your constraints and view setup will need to account for this, or you you will get unsatisfiable constraint errors.

4
votes

While I don't know exactly what you are trying to achieve visually, here are two pieces of advice that might help you:

Firstly, remember to remove all existing constraints from a UIView before adding new ones in code. Don't mix constraints from Interface Builder with constraints in code, it will drive you insane. Example:

- (void)viewDidLoad
{
    [super viewDidLoad];

    // remove all constraints
    [self.view removeConstraints:self.view.constraints];

    // add new constraints
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[webView]|"
                                                                      options:0
                                                                      metrics:nil
                                                                        views:@{@"webView": self.webView}]];

    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[webView]|"
                                                                      options:0
                                                                      metrics:nil
                                                                        views:@{@"webView": self.webView}]];
}

Secondly, you can have NSLayoutConstraint outlets, just like any other outlets from a XIB or a Storyboard. That way, you can adjust certain properties of a constraint at runtime:

// in your header:
@property (nonatomic, weak) IBOutlet NSLayoutConstraint *myConstraint;

// somewhere in your code where you need to adjust the constraint:
self.myConstraint.constant = 100;

I hope this helps a little bit :)

2
votes

I can't give you the answer, but perhaps I can give you some debug advice.. copy the log information into a text editor and replace the memory address information for the views with meaningful names. (use the debugger to find which properties the memory addresses map to if you can't figure it out). Then you will find it a bit easier to understand the log information and trace the conflict. Here is an example (although i'm just guessing your view hierarchy)

"<NSLayoutConstraint:0x145c1990 V:|-(20)-[UINavigationBar:NAVBAR]   (Names: '|':UIView:ROOTVIEW )>",
"<NSLayoutConstraint:0x145bf510 V:[UINavigationBar:NAVBAR]-(0)-[UIView:PAGECONTAINER]>",
"<NSLayoutConstraint:0x145d0550 UIView:PAGEVIEW.top == UIView:ROOTVIEW.top>",
"<NSAutoresizingMaskLayoutConstraint:0x145b3a40 h=-&- v=-&- UIView:PAGEVIEW.midY == UIView:PAGECONTAINER.midY + 56.5>",
"<NSAutoresizingMaskLayoutConstraint:0x145b3a70 h=-&- v=-&- UIView:PAGEVIEW.height == UIView:PAGECONTAINER.height + 113>"

At a guess, the last two autoresizingmasklayoutconstraints look strange to me, they don't look clearly defined.

1
votes

I just ran into a similar situation. If I set any constraints on the UIPageViewController's view, it would cause conflicts with the autoresizing mask constraints.

What works for me is to set translatesAutoresizingMaskIntoConstraints to NO for UIPageController view, then add constraints to the parent view for the pageController view's top, left, width, and height.

  self.pageController.view.translatesAutoresizingMaskIntoConstraints = NO;

  NSLayoutConstraint *constraint1 = [NSLayoutConstraint constraintWithItem:self.pageController.view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:0.0];
  NSLayoutConstraint *constraint2 = [NSLayoutConstraint constraintWithItem:self.pageController.view attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.0 constant:0.0];
  NSLayoutConstraint *constraint3 = [NSLayoutConstraint constraintWithItem:self.pageController.view attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeHeight multiplier:1.0 constant:-60.0];
  NSLayoutConstraint *constraint4 = [NSLayoutConstraint constraintWithItem:self.pageController.view attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeWidth multiplier:1.0 constant:0.0];

  [self.view addConstraints:@[constraint1, constraint2, constraint3, constraint4]];