ioscocoa-touchios8ios8-extensionios8-today-widget

Height of iOS8 Today Extension using Only Auto Layout Gives Broken Constraints


Apple documentation suggests setting the height of Today Extensions using autolayout.

If a widget has additional content to display, you can rely on Auto Layout constraints to adjust the widget’s height as appropriate. If you don’t use Auto Layout, you can use the UIViewController property preferredContentSize to specify the widget’s new height.

However, every example and tutorial I have seen ends up using preferredContentSize.

All of my attempts to set the height via autolayout lead to warnings of broken constraints.

Setting Height Via Autolayout

I started with a fresh xcode template, and a fresh today extension template. The only thing I added to the TodayViewController.m was:

- (UIEdgeInsets)widgetMarginInsetsForProposedMarginInsets:(UIEdgeInsets)defaultMarginInsets {
    return UIEdgeInsetsMake(0, 0, 0, 0);
}

Note: I still get this problem if I just use the default margins.

I constrained the label height, centered the label in the container, and constrained the container height to be the same as the label height:

Constrained Height

This should lead to a label that fills the container at the specified height with no constraint conflicts. Instead I get a constraint conflict:

2014-09-28 10:27:39.254 TodayExtension[61090:2672196] 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:0x7f8b8b62c670 V:[UILabel:0x7f8b8b62d9b0'Hello World'(124)]>",
    "<NSLayoutConstraint:0x7f8b8b583020 UIView:0x7f8b8b62d6e0.height == UILabel:0x7f8b8b62d9b0'Hello World'.height>",
    "<NSLayoutConstraint:0x7f8b8b5888a0 'UIView-Encapsulated-Layout-Height' V:[UIView:0x7f8b8b62d6e0(667)]>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x7f8b8b62c670 V:[UILabel:0x7f8b8b62d9b0'Hello World'(124)]>

Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.

The way that it breaks the constraints, it actually ends up looking like I want it to: Constrained Height View

However, in other projects, it decides to break other constraints and it doesn't look correct. Also, side note: if I try and change the priority of the height constraint it crashes Xcode. So that's fun.

Not Constraining Height

I had hoped that since constraining the height didn't work, maybe if I didn't constrain the container height to the subviews, that perhaps it would figure out the required height to house the subviews and set itself correctly.

I centered the subview and constrained its height:i.imgur.com/PwLmhj9.png

This just led to an extension that uses up the full height of the notification center, and my correctly sized view vertically centered:

i.imgur.com/kKXlocu.png

If I don't center, but instead fix the vertical space to the top layout guide, I get the same thing except that the subview is fixed to the top (but the container is still huge).

What Gives?

I know I could just use preferredContentSize, but then why would Apple say it can be set using Auto Layout constraints? What am I doing wrong?

The examples I have given are obviously contrived. I'm setting the height of the view, so why not just set the height of the container, right? Part of the point of this in an actual project would be to set the height of the widget based on the width using only autolayout.


Solution

  • After some experimenting, and stumbling across "what is NSLayoutConstraint "UIView-Encapsulated-Layout-Height" and how should I go about forcing it to recalculate cleanly" I determined that you can circumvent this problem using either "Height" & "Equal Heights" OR "Height" and Top and Bottom "Vertical Space" (as suggested by Guilherme Sprint), and then decreasing the "Priority" of the height constraint to 999.

    Height Constraint with Reduced Priority

    This isn't the answer I was hoping for, but it does specify the height of the container via Auto Layout of the subview and avoids any warnings about broken constraints.

    My entirely unscientific guess/assumption/conclusion is that iOS is correctly looking at the layout constraints to determine the height of the container view. It then adds a new constraint that is the same height as what it just calculated, but this now over-constrains the height. Reducing the priority on the original developer specified height constraint means the system generated constraint wins out and no warning is generated (would love to hear more about this from someone who actually knows).