I am trying to use springs and struts to automatically resize content. What would the best way to create constaints like this, or is there a better way targeting iOS5+?
[View 1 - fixed height]
(10 pixel padding)
[View 2 - variable height scrollview / tableview, contains content]
You are confusing the issue by referring to “autoresizing constraints”, because “autoresizing” usually refers to the old “springs and struts” system for updating view layout, and constraints are part of the new “autolayout” system (which requires iOS 6).
Anyway, you want to set view 1's autoresizingMask to UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleBottomMargin. This will keep its top, left and right edges pinned relative to its superview, and will keep its height fixed. If you're laying it out in Interface Builder, set it like this:
You want to set view 2's autoresizingMask to UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight. This will keep all of its edges pinned relative to its superview, and will let it get taller/shorter (and wider/narrower) in sync with changes to its superview's size. If you're laying it out in Interface Builder, set it like this:
It's up to you to lay them out with a 10 point space between them. If you do that, and set the autoresizing masks as I have described, then they will keep the 10 point space automatically when the superview changes size.
Related
Simple goal, but it demonstrates situations where Auto Layout gives me a headache. I want to have a stack view with a width of 256pt and a dynamic height based on layout (I shouldn't have to manually specify the height).
Inside it should be an image view sized 64pt x 64pt, which should also be centered horizontally as well as constrained 8pt from the superview's top. Note that the image view isn't the only child, hence why the stack view's height must be sized dynamically.
Auto Layout now tells me there's a conflict between the 256pt width constraint of the stack view and the 64pt width constraint of the image, as well as some mysterious "leading = Image.leading" and "trailing = Image.trailing" conflict which I can't even delete nor find.
Am I missing out something here regarding Auto Layout? I expect all logic to be contained in the interface builder, so no code should be required.
Running Xcode 9.1
Layout image
There is nothing to confuse. iOS clearly telling you the issue.
StackViews take size based on the size of child components (this is called implicit size) unless its been overriden manually as in your case which is 256pt.
Because stackView is just a container for multiple childViews stacked either horizontally or vertically, now because you have added only one imageView to it, it adds the leading and trailing constraint to it which makes absolute sense because you added a single view to the stack of view's , now what should stackView do? stretch childView (in your case imageView) to its own size.
But then you did not allow it because you added width constraint to imageView now when it tries to increase the imageView's width imageView's constraint wont allow it.
Hence it is complaining that there are too many conflicting constraints. Thats all :)
some mysterious "leading = Image.leading" and "trailing =
Image.trailing" conflict which I can't even delete nor find.
You cant delete them because, imageView is the only view inside stackView. Because there is only one child view to stack, stackView will start from left side (leading) to right side (trailing). Because now stackView has its own width it tries to change the width of imageView to reflect the same! But images width constraint prevents it from happening.
What are you trying to achieve with imageView added to stackView. If there is only one view in stackView, adding stackView does not make any sense. Reconsider what you are doing.
Finally, when you have only one childView in stack view, adding horizontal center does not make any sense (no matter vertical/horizontal stackView).
Suppose that I have the following view controller and this is how I want to see it on all iPhone:
If I run it on iPhone 6 it has the following look:
Here you can notice that UITableView not fit the whole screen and UIImageView doesn't placed at the bottom of the screen.
How can I achieve the required behavior via constraints in XCode 6? I thought that I need the following constraints:
Leading space and top space to container margin for UITableView
Bottom space and trailing space to container margin for UIImageView
Vertical Spacing between UITableView and UIImageView
But it doesn't work as expected even after auto-resolve constraints issues:
Thanks in advance.
Ok, a few things here:
Each view needs enough constraints to define it's x and y position, and it's width and height unambiguously. To start with, go back to Interface builder and delete all of your constraints and lay out the view as you would like it to look. You want to have control over every constraint added, don't let IB automatically resolve the issues, as in all likely hood it won't do what you want.
Do you have an image that is the size you want it to be on screen, once you've factored in #2x, #3x etc? If so, then your job will be easier, as the width and height of the image view can be defined by the width and height of the image (ie the image view's intrinsic content size).
In order to use Autolayout effectively, you need to think about your view holistically, and think about how you want your views to behave when the screen size changes, be clear in your head about the behaviour.
To achieve the layout you want, I would do the following:
Constrain the tableview's leading, top and trailing edges to the superview, with a constant value of 0. This means it can get wider and thinner with the device, it will stretch horizontally, but always stick to the top. This has defined the tableview's x and y position, as well as it's width (height still to go, but keep reading...)
Constrain the image view to match the horizontal centre of it's superview (x position defined) and constrain it's bottom edge to the superviews bottom edge (y position defined). If've you've got the right sized asset, then that will take care of the width and height too. If not, you could go ahead give it explicit width and height constraints.
Now we can constrain the tableview's bottom edge to the top of the image view, with a constant of 0 (ie touching). Note we haven't give the table view an explicit height constraint, so as the device screen grows vertically, the table view will stretch vertically.
Autolayout is hard at first. I'd recommended lots of reading to get over the initial hump, really get to know what a constraint is doing, it's limitations, and the way in which the system parses constraints to translate them into frames. This book is really good, and really helped me learn:
http://www.amazon.co.uk/Auto-Layout-Demystified-Mobile-Programming/dp/0321967194
Best of luck
First make sure you have selected the correct size class. The 'Compact Width | Regular Height' size class must be selected in the Interface Builder. Now add the Trailing space,Leading Space, Top space and Bottom space constraints to the table view. For the image view set the view mode to Aspect fit and add the constraints : Align Center Y ,Top space,Bottom space, Leading space, Trailing space and Aspect Ratio .
I noticed some very strange behavior when trying to fill a view with a child view using autolayout. The idea is very simple: add a subview to a view and make it use all of the width of the parent view.
NSDictionary *views = #{
#"subview":subView,
#"parent":self
};
This does not work:
[self addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[subview]|"
options:0
metrics:nil
views:views]];
The subview doesn't use the full width of the parent view.
But this works:
[self addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[subview(==parent)]|"
options:0
metrics:nil
views:views]];
I would expect that both would work as intended. So why is the first example not working? It is what Apple recommends in the following technical note:
https://developer.apple.com/library/ios/technotes/tn2154/_index.html
EDIT: (removed irrelevant information)
Here are the constraints you posted for your “first example”:
Here are the constraints you posted for your “2nd example”:
I see two differences in these structures, which I have highlighted in red in the diagrams:
The first (broken) case has a constraint (0x8433f8f0) on the near-root UIView 0x8433f8f0, pinning its width to 320 points. This is redundant, because the bottom-level views are constrained to 160 points each, and there are sufficient constraints to make all the ancestors of those narrower views be 320 points wide.
The second (working) case has a constraint (0x7eb4a670) pinning the width of the near-bottom UIView 0x7d5f3fa0 to the width of the DetailWeatherView 0x7d5f3270. This constraint is redundant because 3fa0's left edge is pinned to 3270's left edge, and 3fa0's right edge is constrained to 3fa0's right edge. I assume the (==parent) predicate adds this constraint.
So you might think, each case has one redundant constraint, so what? No big deal, right?
Not quite. In the second case, the redundant constraint is truly harmless. You could change the width of either (or both) of WeatherTypeBoxView and AdditionalWeatherInfoBox, and you'd still get a unique solution.
In the first case, the redundant constraint is only harmless if the width of the views doesn't change. If you change the 320-width constraint on f8f0 without changing the 160-width constraints on the leaf views, the constraint system has no solution. Autolayout will break one of the constraints to solve the system, and it might well break that 320-width constraint. And we can see that the 320-width constraint, with its UIView-Encapsulated-Layout-Width annotation, is imposed by the system to force this view hierarchy to conform to some container's size (maybe the screen size; maybe a container view controller's imposed size).
I don't know why your first case has this extra top-level constraint and your second doesn't. I'm inclined to believe you changed something else that caused this difference, but autolayout is mysterious enough that I'm not 100% convinced of that either.
If your goal is to make this view hierarchy fill its container, and keep the leaf views equal width, then get rid of the 160-width constraints on the leaf views and create a single equal-width constraint between them.
EDIT: as Ken Thomases has pointed out, I'm completely wrong here!
Yes, both of the constraint systems you specify should cause the parent and subview (or parent and contentView) to have the same width.
So I would ask, are you absolutely sure that what you're seeing is the contentView fail to fill the parent? Is it possible what you're seeing in the broken case is that the parent is actually being shrunk to fit the contentView, and you're not noticing it because the parent has a transparent or otherwise invisible background?
To check this, set the contentView and the parent to have different opaque background colors. If I am wrong, you will clearly see a region of background where the parent extends out with a width greater than the contentView. If I am right, the contentView will completely cover the parent's width.
Here is why I am (maybe presumptuously) questioning your stated fact about what you are seeing:
The key difference in your log outputs are that, for the broken case, we see an unexpected constraint that holds a UIView to a fixed width of 320:
<NSLayoutConstraint:0x8433f8f0 'UIView-Encapsulated-Layout-Width' H:[UIView:0x841b9f40(320)]>
What view is that? Based on the constraint name "UIView-Encapsulated-Layout-Width", that it is associated with a UIViewControllerWrapperView, and that the width is being held to 320 (width of an iPhone), we can deduce this constraint is automatically created by UIKit to constrain the size of a UICollectionView.view to the size of the screen. Looking at all the constraints, you can further see that this width=320 constraint is passed down via a chain of constraints (encapsulated-view -> DetailView -> DetailWeatherView) until it ultimately determines the width of DetalWeatherView, which is your parent, and which uses superview-edge-offset constraints to hug the contentView, which should therefore also get that same width of 320.
In contrast, for the case that works, you can also see the same chain of constraints that should constraint the contentView.width to equal the width of the top encapsulated layout view. But the difference is there is no constraint anywhere that holds anything to a fixed value of 320. Instead, there is only a left-alignment constraint.
So I would expect to see that in both cases contentView.width == parent.width, but that in the broken case width=320 whereas in the working case the width is being determined by lower-priority internal constraints within contentView that allow it to expand to a value greater than 320, perhaps to its intrinsicContentSize.
The reason this was not working as expected was this:
The parent view is a UISCrollView, and the horizontal constraints are set to "H:|[contentView]|", the contentSize of the scrollview will adjust itself to the width requested by contentView, not the other way around. So the autolayout engine will first determine the dimensions of contentView and then adjust the contentSize of the parent (scrollview) to the same width. If contentWidth is narrower than the parent, it won't stretch because the contentSize of the parent (scrollview) will shrink to the size of contentView.
For regular views (not scrollviews), the width of the parent view is fixed so it will first layout the parent view and then the child view(s).
By forcing the width of contentView to the same width as the parent scrollView, contentView will always be the same width as the parent scrollview, which is what I wanted (and expected).
I cant for the love of god the the hang of this resizing superview.
I have a UIView *superview with 4 UILabels. 2 function as header for the 2 others.
The content in all 4 are dynamic coming from database.
SizeToFit vs SizeThatFits:(CGSize) vs UIView systemLayoutSizeFittingSize:, passing either UILayoutFittingCompressedSize or UILayoutFittingExpandedSize.
I use autolayout programatically and have set the superview height to be equal or greater to a dummy number.
where and how do I use these SizeToFit vs sizeThatFits:(CGSize) vs UIView systemLayoutSizeFittingSize:, passing either UILayoutFittingCompressedSize or UILayoutFittingExpandedSize. I have read a lot of tips here on stack but ended up with nothing.
DO I need to recalculate the constraints for the superview somewhere specific. Maby setting the height to be ´#property` in its controller class and remove and readd it?
Atm I have tried to put everything everywhere and then some. Still I get the same size end result with the dummy height and text floating outside. Even after setting clipsToBound on subview.
I am scratching my hair of.. help
If you're using Auto Layout, here's what you need to do:
Make sure you aren't adding fixed width and/or height constraints to any of your subviews (depending on which dimension(s) you want to dynamically size). The idea is to let the intrinsic content size of each subview determine the subview's height. UILabels come with 4 automatic implicit constraints which will (with less than Required priority) attempt to keep the label's frame at the exact size required to fit all the text inside.
Make sure that the edges of each label are connected rigidly (with Required priority constraints) to the edges of each other and their superview. You want to make sure that if you imagine one of the labels growing in size, this would force the other labels to make room for it and most importantly force the superview to expand as well.
Only add constraints to the superview to set its position, not size (at least, not for the dimension(s) you want it to size dynamically). Remember that if you set the internal constraints up correctly, its size will be determined by the sizes of all the subviews, since its edges are connected to theirs in some fashion.
That's it. You don't need to call sizeToFit or systemLayoutSizeFittingSize: to get this to work, just load your views and set the text and that should be it. The system layout engine will do the calculations for you to solve your constraints. (If anything, you might need to call setNeedsLayout on the superview...but this shouldn't be required.)
Use container views
In the following example I have a 30x30 image, and the UILabel is smaller than the containing view with the placeholder text. I needed the containing view to be at least as big as the image, but it needed to grow to contain multi-line text.
In visual format the inner container looks like this:
H:|-(15.0)-[image(30.0)]-(15.0)-[label]-(15.0)-|
V:|[image(30.0)]|
V:|[label(>=30.0)]|
Then, set the containing view to match the height of the label. Now the containing view will ride the size of the label.
As #smileyborg pointed out in his answer, connecting the content rigidly to the superview informs the layout engine that the simple container view should cause it to grow.
Yellow alignment rectangles
If you want the yellow alignment rectangles add -UIViewShowAlignmentRects YES in your scheme's list of run arguments.
This almost follows #smileyborg answer and comes with a concrete example.
Won't describe all constraints, but those related to the calculation of the height of UI objects.
[Label] Labels must not have a fixed height constraint, in this case, AutoLayout won't resize labels to fit the text, so setting edge constraints is the key. (green arrows)
[Subview] Steps 1 and 3 are very easy to follow, but this step can be misunderstood. As in the case with labels, subviews must not have height constraint set. All subviews must have top constraint set, ignoring bottom constraint, which can make you think will trigger unsatisfied constraint exception at runtime, but it won't if you set bottom constraint for the last subview. Missing to do so will blow the layout. (red arrows)
[Superview] Set all constraints the way you need, but pay big attention to the
height constraint. Assign it a random value, but make it optional, AutoLayout will set the height exactly to fit the subviews. (blue arrows)
This works perfectly, there is no need to call any additional system-layout update methods.
This was made dramatically easier with the introduction of Stack Views in iOS 9. Use a stack view inside your view to contain all your content that resizes, and then simply call
view.setNeedsUpdateConstraints()
view.updateConstraintsIfNeeded()
view.setNeedsLayout()
view.layoutIfNeeded()
after changing your content. Then you can get your new size by calling
view.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
if you ever need to calculate the exact size required for a view.
I have a tableview with cells containing text views as well as imageviews. My project is currently using AutoLayout. My goal is to get the imageview to display in fullscreen when it is tapped. One option is to use a modal view controller, but I want to have this work sort of like the way tapping on images in the facebook app works, the app centers the image and fades the background.
Since I'm using autolayout, I cannot simply set the frame of the imageview to fill the screen. Instead, I need to use autolayout constraints. My image view has 5 constraints, a constraint setting a distance from the bottom of the cell, as well as the left an right sides, and one controlling the image height. The last is a vertical space constraint between the textview above the image view and the top of the image. While this would appear to conflict with the height and bottom constraints, for some reason interface builder forces me to have this. To avoid problems, I set this constraint's priority to be less than 1000 (the image should never overlap the textview anyways, since the tableview cell height is set so everything will fit perfectly).
To center the image, I set the distance from the left and right to be zero and remove the vertical space constraint. In order to center the image, I replace the bottom space constraint with a center y alignment constraint to the UIWindow as opposed to the tableviewcell. I want to have it be in the center of the screen, not the cell.
To get the main window I use this:
AppDelegate* myDelegate = (((AppDelegate*) [UIApplication sharedApplication].delegate));
//access main window using myDelegate.window
Then, to set the constraint:
//currently sets the distance from the bottom of the cell to 14
//changing it...
[cellselected removeConstraint:cellselected.imagebottomspace];
cellselected.imagebottomspace = [NSLayoutConstraint constraintWithItem:cellselected.viewimage attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:myDelegate.window attribute:NSLayoutAttributeCenterY multiplier:0 constant:0];
[cellselected addConstraint:cellselected.imagebottomspace];
However, this doesn't work. The changes in the width and height of the image view apply just fine. However, when readding the imagebottomspace constraint, I get an unsatisfiable layout--apparently the constraint conflicts with another constraint which sets the distance between the bottom and the image view to 14, the very constraint I just removed. So it seems that it isn't actually removing the constraint.
When I proceed and let the app break a constraint, the imageview moves, but to the wrong place. It isn't centering in the screen. It moves way up and off the screen.
Obviously what I'm doing isn't right. What am I doing wrong?
So I guess you want something like this:
First, you need to know that as of Xcode 4.6.3, the nib editor (“Interface Builder”) has a bug when setting up constraints in a table view cell. It should create the constraints between the subviews and the cell's content view, but instead it creates the constraints between the subviews and the cell itself. This tends to screw up layout at runtime. (This bug is fixed in Xcode 5 and later.)
The consequence of this is that you should either remove all of the constraints that were in the nib and recreate them in code, or just get rid of the nib and create the cell's entire view hierarchy in code.
Second, there's an easier way to do the image zooming. Here's the basic procedure when a cell is selected:
Convert the selected cell's image view bounds to a CGRect in the top-level view's coordinate system.
Create a new image view just for zooming and set its frame to that CGRect. Set its userInteractionEnabled to YES. Set its autoresizingMask to flexible width and height. Add a tap gesture recognizer.
Add the new image view as a subview of the top-level view.
Set the cell's image view's hidden property to YES.
In an animation block, set the new image view's frame to the top-level view's bounds.
Disable the table view's panGestureRecognizer.
When the new image view is tapped, reverse the procedure:
Convert the selected cell's image view bounds to a CGRect in the top-level view's coordinate system.
In an animation block, set the zoomed image view's frame to that CGRect.
In the animation completion block:
Remove the zoomed image view from its superview.
Set the cell's image view's hidden property to NO.
Enable the table view's panGestureRecognizer.
Since you're not moving the original image view, you don't have to mess with its constraints. Hidden views still participate in layout.
Since you're creating the new image view in code, it will have translatesAutoresizingMaskIntoConstraints set to YES by default. This means that you can just set its frame. Auto layout will automatically turn the frame into constraints.
You can find the full source code in this github repository.
I've just come across a similar issue. I think that the reason for these problems are that the views embedded in UIScrollViews exist in a different bounds system to those of the views outside it. This is effectively how scrolling works in the first place, think of it as just applying a variable offset to the views it contains. Autolayout doesn't know how to translate between these different coordinate systems so any constraints that bridge across aren't going to be applied the way you expect.
To quote from Erica Sadun's excellent book iOS Auto Layout Demystified (from the section 'Constraints, Hierarchies, and Bounds Systems'):
"Be aware of bounds systems. You should not relate a button on some
view, for example, with a text field inside a separate collection
view. If there's some sort of content view with its own bounds system
(such as collection views, scroll views, and table views), don’t hop
out of that to an entirely different bounds system in another view."