When would you need to override alignmentRect instead of intrinsicContentSize in some UIView subclass?
Is it just for when the position as well as the size is different? If you supply an alignmentRect, is the content size ignored?
intrinsicContentSize is used to tell the layout system what the size of a view is. Use this to inform the layout system how large to draw a view based on its contents. Content size, not position.
For positioning, there are two things you should look at:
layoutMargins, which is used to determine the layout of the inside of a view (i.e. padding on the left and right of a stackView's contents), and alignmentRectInsets, which is used to inform the object holding your view how it should align your view. For example, if you have a shadow or attached view (like a notification dot), you might want to lay your view out centered on the primary feature, not including the shadow or dot's width/height.
The article Auto Layout in iOS 8 - Layout Margins at Carpeaqua does a good job of explaining layout margins with examples, and the article Auto Layout and Alignment Rectangles at Use Your Loaf does a good job of explaining and showing why you might want to use an alignmentRectInsets.
Related
When i use Auto Layout i typically attach objects to bottom, instead of baseline. But what is - baseline? I could not find good answer from apple docs, either on SO.
From here i only figured out that baseline is just a horizontal line, that is upper from bottom, but lower the centered Y line.
Can someone provide explanation what is it and why should anyone use it? For me it is much more easier to just attach view to bottom.
According to Apple docs, the baseline of a UILabel or other UI element with text is the "vertical alignment of the text within the label". In the docs for UIView it states:
When you make a constraint to a view’s baseline attribute, Auto Layout
uses the baseline of the view returned by this method. If that view
does not have a baseline, Auto Layout uses the view’s bottom edge
View A layouts it's subviews by calculating and setting their frames in layoutSubviews.
View B uses constraints to layout it's subviews.
View A contains a number of View B instances.
View A needs to know the size of its subviews when it layouts them, so it calls sizeThatFits: on them.
View B's height depends on it's width. In my example it has a multiline UILabel inside it.
How should B implement it's sizeThatFits: so it returns the correct size taking both the size parameter passed to sizeThatFits: and the constraints in account?
What I've tried/considered so far:
systemLayoutSizeFittingSize: - doesn't work because it doesn't take into account an exact size passed to it. It can't be used to specify an exact width for e.g a view that grows vertically as it shrinks horizontally. It only gives either the most compressed size or the most expanded size.
intrinsicContentSize- this is used to pass information to Auto Layout as far as i understand from the docs. What we want is to get information from Auto Layout.
This is how i've reasoned so far. Please correct me if i've made a mistake. And please help me, i'd be very grateful if someone could point me to how this is done!
edit: Also, maybe there is some entirely different way to do this, without even using sizeThatFits:? The only requirement i have is that i can continue using frame layout in the "outer" view (View A) and auto layout in the "inner view" (View B).
edit2: Added a picture to illustrate the exact case i must solve
edit3: Forgot to mention, i need to support iOS 7.0
From the picture it would appear that a UIStackView might be a better approach for View A?
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIStackView_Class_Reference/
and then just constrain with a margin offset the bottom of the View A UIStackView to the bottom of the last View B
I would use Autolayout for the lot. You can still specify exact sizes and positions, you just do it with constraints instead of with frames.
TL;DR: What's the best way to layout over-sized scroll-view content in Interface Builder?
I am trying to figure out the least painful way to layout offscreen / scrolling content in Interface Builder. My current approach has been to tweak scrollview content width and height constraints to see all content in IB, before reverting to proper contraints for building, but this gets to be a hassle. That said, I would prefer to do more more graphically, and less programmatically for general ease of editing.
The general approaches that I can think of are:
use embedded segues to build up views in a size-accurate fashion (the most intuitive, flow-charty approach)
tweak constraints for editing, reset for building
use placeholder values / constraints in IB (haven't tried this yet)
layout over-sized content programmatically
In more detail, I am trying to build a vertically scrolling view comprised of sub views. Each sub view is self contained and can potentially appear in more than one context and I would like to keep them isolated (in fact, I am embedding them as well, but that does not affect the question). What I would like to do, is use IB to layout a tall composite content view. I would then like to create the outer scroll view with a single content view again of a container view, embedding the composite content view. Ie. the grey content view on the left embeds the taller red / blue content on the right, and I am wondering if I can deduce the height, so it could be device agnostic (the views are square, so they will take their width from the device)
So summarized, is there anyway that I could derive the hosting scrollview's contentView height from the actual height of the content of the embedded view, or should I instead use approaches 2, 3, or other?
To be clear, Auto Layout requires that scroll view content views define constraints for the edges, and width / height dimensions. In my case, I would like to be able to preset the width to the device width, but leave the height to be derived from the red/blue content view. IB won't let me drag constraints between the embedding and embedded scenes in Storyboard, and when I leave the field missing, it uses the prototype values for the content height.
Is it recommended to use placeholder sizes (3) in IB to make the content visible for editing, knowing that run-time constraints will render it properly (ie over-size scroll views in Storyboard so all scrolling content is visible)? Conversely, is it foolish to even try - should I do all scroll-view content composition programmatically (4)?
Thanks in advance.
Edit:
Placeholder constraints allow you to get by, but force you to change the priority of the run-time constraints, and trigger IB / layout errors and warnings for conflicting constraints, despite selecting the placeholder checkbox on conflicting design-time-only constraints.
Use a UITableviewController with static cells. The cells will be your content views.
You can scroll the tableview down in IB to see the cells at the bottom and you get a nice extra behaviour like scrolling the views to the correct position when the keyboard is opened so it doesn't hide the textfield you clicked on.
And you don't have to worry about the scroll view at all.
If you want to reuse your content views you can make the table dynamic of course and provide the content views by code.
I've got a scroll view contained directly inside the content view of a view controller, at full size in both dimensions. The top, bottom, leading and trailing space constraints from the scroll view to its superview (horizontal) and the layout guides (vertical) are all set to 0. The VC is eventually meant to be nested as a child view controller in one or two places. I'm using a Storyboard.
I've placed a number of elements inside the scroll view and constrained them to it, but I'm seeing various kinds of strange behavior. Below is a screenshot with all the subviews of the scroll view selected to display their constraints. The scroll view's four constraints to the top level view are not visible in it. The view controller has been set to Freeform size, with its top level view (and, accordingly, the scroll view's content view) 616 pts high, guaranteeing that scrolling will be necessary at runtime.
Before analyzing the screenshot, here's a list of things that I'm trying to achieve.
The vertical spacing between elements is set by the designer and fixed. (BTW, none of the vertical constraints, text styles etc. in this wireframe are final yet; the whole image is for illustrative purposes only.)
All the labels (except the topmost one) should start at their intrinsic size, expand up to the width of the scroll view (minus the standard HIG horizontal space of 20 pts on both sides).
Buttons are unlikely to be much bigger than this, but in case of localization surprises, we want them to behave just like the labels. (There's an extra vertical ≥ constraint on "Another Button"; it's irrelevant to this question.)
The web view has a fixed height, and its width should be determined by the width of the scroll view; standard 20 pt horizontal space on both sides.
The text views have a minimum height (67 pts here), but they should expand vertically if the contained text is too big to fit. None of them are editable or scrollable. Like the web view, they're horizontally spaced the standard 20 pts apart from the leading and trailing edges.
As you can see, none of the elements have explicit width constraints. The whole thing relies on the leading and trailing space constraints between the elements and the scroll view. The layout, in my mind, would somewhat gracefully work on hypothetical wider-than-320 pt iPhones of the future without changes to the constraints. It would also work after rotating to landscape orientation (it might look a bit silly, but it would work).
I'll go through the points step by step, referring to the screenshot where necessary.
1: This works, nothing out of the ordinary here.
2: The leading constraints of the labels are all simple Equal 20 pt standard spaces. The trailing constraints are Greater Than or Equal 20 pt standard, ostensibly to allow them to grow to be scrollView.frame.size.width-40 wide, but no wider.
3: Same as 2.
4 and 5: Here's where it gets interesting. The web view and the text views are all listed as Misplaced Views, with IB saying their frames will be different at runtime. The orange dashed borders denoting the correct frames only reach horizontally as far as the longest element with a Greater Than Or Equal trailing constraint; here, it's "A Button With A Long Title", whose right edge is where the dashed border edges end.
Constructing this set of views and their constraints, I expected to have some trouble. I knew it would be tricky to have UITextViews that grow vertically taller than the ≥ 67 height defined here, perhaps only possible through code. Getting the labels and buttons to work as specified above through IB alone seemed a bit iffy, too.
What I didn't expect was the web and text views' reported correct frame being only as wide as the widest label or button. It seems that with this setup, the scroll view won't actually be 320 pts wide, but rather only as wide as necessary to fit the longest element and its spacing, and the web and text views are expected to comply. Given that the scroll view is firmly constrained on all sides to the top level view, which is set to be 320 pts wide, I have no idea why this is. SOMETHING must obviously define the initial width of the scroll view, but why aren't the constraints I've made from the scroll view to the top level view doing that?
Given the specifications above for this set of views, what do I need to change to make it happen?
This case demonstrates the fact that I truly do not properly understand Auto Layout yet, and I hope that the answers will enlighten me about many of its crucial aspects.
With respect to Xcode's warnings about misplaced views, select the view controller in storyboard, tap the "Resolve Auto Layout Issues" button in the lower-right-hand corner of the canvas (it looks like a Tie Fighter from Star Wars), then select "Update All Frames in View Controller". This forces all of your views to reflect their constraints.
Using Auto Layout with UIScrollView is a different animal; so much so that Apple felt it necessary to release a Technical Note on the issue: https://developer.apple.com/library/ios/technotes/tn2154/_index.html
When you connect all those constraints between subviews and the inside walls of a scroll view, the result is probably not what you expect. When you pin a subview to the sides of a scroll view, you are not in fact determining the subview's position relative to the scroll view. Instead, you are determining the scroll view's contentSize. This is weird.
You are using Apple's so-called pure Auto Layout approach from the Technical Note. With this approach, the subviews' constraints must dictate the scroll view's contentSize.
Let's take just one of your subviews and ignore all the rest, say one of the text views. And let's only think about that text view along the horizontal axis. If you wanted the text view to be constrained by the width of the scroll view without any horizontal scrolling, you would need to install a fixed-width constraint on the text view that was exactly the width of the scroll view's bounds minus the spacers. After doing this, the content size width would be the sum of the left spacer, the width of the text view, and the right spacer.
Unfortunately, you cannot install a constraint that establishes a relationship between the width of the text view and the width of the scroll view's bounds. And that's really too bad.
I don't actually recommend installing a fixed-width constraint on the text view. Instead, I would start over and use Apple's "mixed approach" from the Technical Note.
With the mixed approach, the subviews' constraints don't determine the scroll view's contentSize. Instead, you must explicitly set the scroll view's contentSize and the frame of a container view (i.e., a UIView content view).
Let's go back to that UITextView and the horizontal axis. Using the mixed approach, you could leave the constraints for the text view as they are (i.e., no fixed-width constraint). You could explicitly set the width of the scroll view's contentSize and the width of content view's frame as early as viewDidLoad. You could explicitly set these values to self.view.bounds.size.width because your scroll view hugs the sides of the main view.
To implement the mixed approach, you will have to instantiate the content view (UIView) in code and not set its translatesAutoresizingMaskIntoConstraints property to NO. By extension, you'll probably need to create your constraints for all those subviews in code as well (I don't know of any way around this). The visual formatting strings are tedious and repetitive, but they're actually easier to work with than constraints created in IB when configuring complex layouts (your layout is sufficiently complex).
I used the mixed approach to solve a SO challenge here: https://stackoverflow.com/a/21770179/1239263. Unfortunately, the solution hasn't been vetted yet. It's always possible that I'm nuts and I don't know what I'm talking about.
In mechanical CAD software which uses concepts similar to autolayout constraints, you can often add a 'derived constraint'. This has no effect on the layout, but will allow you to directly read the value of an important dimension.
For example, consider the following layout for a view with two subviews:
32 64
|------| |------|
|-[imgOne]-[imgTwo]-|
|-------------------|
w
The width of the view is the sum of the default edge spacing on the left and right, the default spacing between image views, and the two width constraints (32 and 64) applied to the image views.
I would like to know w at runtime.
In theory, this should be view.bounds. However, it's not always safe to read that property (as the view may not have updated its layout constraints yet).
Is it possible to add a constraint which has no effect on the view size, but which will have its .constant property updated once the layout is complete?
(I have tried adding a width constraint to the view with a priority of 1, but .constant always reads the nominal value, instead of the actual value.)
Adding a "derived" constraint will put you in the same hole you began in--waiting for Auto Layout to finish laying out your views. It's all about timing. When using Auto Layout, a good place to read the final geometries of your views is in your view controller's viewDidLayoutSubviews method. In other words, read the bounds of the view in viewDidLayoutSubviews.