Hacking Constraints in Storyboard Files

When Interface Builder doesn't do what you want Hack the Storyboard XML

I like Autolayout and the concepts behind it. I also like using Interface Builder to see the relationships I'm creating but there isn't quite the right degree of flexibility and control (at least as of Xcode 5.1.1).  This post shows how you can convince Interface builder to do your will when it doesn't want to let you create quite the layout that you want you can directly edit the Storyboard XML to enable the effect that you want (and subsequently view it and tweak it in Interface Builder).

The Problem

Scroll view (including UITextViews) Content Placement in iOS 7

In iOS 7 the top and bottom bars are translucent so that if content scrolls underneath them a hint of it can still be seen. To support this effect the UIScrollView containing the content needs to extend underneath the bars BUT the content offset of the scroll view needs to be such that the default position and the limit of scrolling (where over-scroll bounce starts) is in the correct visible place. To add a complication the height of the top bar depends on several factors including the orientation of the device.

Adjust Scroll View Offsets

automaticallyAdjustsScrollViewInsets is a property of UIViewController assist you. This will if enabled change the content offset of scroll views (probably best to have only one) within the view controller's main view to match the height of the top bar. This means that if the scroll view top edge is aligned with the top of the view controller's main view then the content offset will be correctly adjusted to align with the bottom of the menu bar.

Interface Builder Can't Create a Constraint with the Top of the Superview

Unfortunately using Interface Builder (at least in Xcode 5.1.1) it seems to be impossible to set the constraint for the top of the scrollview to be related to the top edge of the view. It only allows you to apply constraints with the "Top layout Guide" which is essentially where the bottom of the navigation bar will appear. The leading and trailing space to container options shown only are for the horizontal constraints.

Now one way to handle this would be either to create or change the constraints within the code (probably within viewDidLoad) but while I have done things like that before I dislike splitting the layout code and would rather have it contained in one place. I think it is perfectly reasonable to put all of the layout in the code for those that prefer that but if you are using InterfaceBuilder and storyboard files I think it is best to put all of the layout that you possibly can in there.

The Solution - Manually Editing the Storyboard XML

Before you follow these steps I suggest that you ensure that the latest Storyboard is checked into your chosen version control system for I make no promises of success and there is every chance that you could accidentally end up with an invalid Storyboard file.

Step 1 - Set Up the Constraints in Interface Builder

Apart from the top constraint it should be possible to set up the rest of the constraints as you want them. It is also worth adding the top constraint between the scrollview and the Top Layout Guide as this is the one you will be modifying. I also suggest that you add a constraint on leading/trailing space to container as it will be a handy place to find a reference that you will need in the manual edit. If this isn't what you want you can remove it after the manual edit.

Step 2 - Rename constraints of interest

To make it easy to find the right constraint and the necessary reference I renamed the constraint I wanted to change and the constraint between the scrollview and the leading edge of the container. Then I saved the file in Interface Builder.

Step 3 - Check the Diff to find the relevant lines

The quick way to see the constraints you are interested in is to see the lines where the renamed constraints are is by looking for them in the diff since the last commit.

As you can see in the diff for me it was lines 657 and 659. The names I added are in the userLabel attribute of the constraint. I made an intermediate commit at this point so that the manual edits could be reviewed on their own.

Step 4 - Edit the Storyboard in a text Editor

Knowing the line you can jump right to the correct place and get straight to editing. Assuming the constraint direction is from the scrollView to the Top Layout Constraint you need to change the "secondItem" to that from the horizontal and the "secondAttribute". You can also remove the "constant" at this point (or set it to zero in which case IB will remove it next time it writes the file) or you can do that in IB later.

You can see in this diff that the value of "secondItem" has been taken from line 657.

Step 5 - Review your Changes in IB

You should be able to see that the constraint now references the superview rather than the layout guide.

Check the result on your Device

Make sure you have enough content in the view to ensure that you can test the scrolling and in particular that all the content is visible when you scroll to the top. Ensure the content is properly aligned with the view rotated (if you allow rotation).

Bottom alignment and Keyboard Handling

In my view I elected to "Hide Bottom Bar on Push" so for the bottom constraint the Bottom Layout Guide works fine although if that wasn't the case you could make a similar change for the bottom too. To handle keyboard appearances is a whole separate article and plenty of people have written it already, I largely followed the approach in this article.