When using autolayout, calling intrinsicContentSize seems to be the method to determine what CGSize is required to properly fit the views content.
However, this method is only supported for a limited number of existing UIViews.
Anytime that I make a custom view, even if it is something as simple as a UILabel inside of a container UIView, that containing view is unable to determine its intrinsicContentSize (returns -1).
I don't understand why the view is able to properly be displayed on the screen yet the view doesn't even know its own height...
The UILabel in a container view is a simple example but I'm dealing with slightly more complicated UIViews where there are maybe 15 views nested within eachother. In order to determine the size of the view which contains all of its subviews, I have to manually create my own intrinsicContentSize method and do very time consuming work where I have to sum up all the heights of the subviews plus add to that all of the constraints.
This process is terrible. It's very easy to miss out on a height somewhere by forgetting to add the height of one of the subviews or constraints. Also, the matter is further complicated by the fact that with dynamic subviews. For example, if the view has 2 columns of dynamic subviews, you need to manually find the height of the subviews+constraints for each column, compare these heights and return the larger of the two. Again, this is a simple example but often it's not so simple, causing many many migraines.
Back to what I was asking earlier. How can iOS display the view yet not even know how tall the view is? There must be some way to find out what that height is. Thanks for reading.
Here is an image to help visualize what I want.
Are all your subviews using auto-layout themselves? I mean that if your using auto-layout to place MyCompositeObject, is that composite object using constraints internally to place its many objects? I've found that if so, then the intrinsicContentSize will account for all the subviews, but if not, your UIView's intrinsic content size is going to end out returning something inaccurate and small.
Related
I have a stack view that contains three UICollectionViews, set up to give each of them equal vertical space. That stack view is set to be the height of half of the display, so that it uses more space on larger devices. This has been set up in Interface Builder.
So, I need to set the cell size of the UICollectionView at runtime, since until we are running, I don't know what the actual size of the cells will be. I want them to be square, so I just want to take into account the height of the UICollectionView, subtract out the top and bottom section insets, and set the itemSize to the resulting size.
I attempt to do this in viewDidLayoutSubviews, since by then I figure that the initial heights of the collection views have been set. However, they appear to be set to 1000x1000 (even though they are a much more reasonable size in the storyboard), and so I compute a cell size based on a collection view height of 1000. This is too large, but I figure that I'll get called again and get another chance to recompute it. And I do, but not before UICollectionView complains loudly that the itemSize is incorrect (ie. too large to fit in the collectionView, which now has the "correct" size.)
What is the best way to get the behavior I'm looking for without the warnings from UICollectionView? Setting the collection view item size at runtime based on the eventual size of the UICollectionView is something I've struggled with in the past, and there never seems to be the "right" time to set the itemSize. I don't want to dynamically return it, if only because it's not something that changes during the life of the program. There just seems to be some inconsistencies that occur when laying out the views initially.
It seems odd to me that the collection view comes in with an initial size of 1000.0 by 1000.0, but I'm not sure how or why to fix that - perhaps it has something to do with being embedded in a stack view?
Edited to add: It is almost certainly the UIStackView that is causing the layout issues. I created a dummy project to test the size of a UICollectionView when it is the top level view vs embedded in a UIStackView. If it is not embedded, when viewDidLayoutSubviews is called, it has been properly sized to fit the bounds of its superview. However, if it is inside of a UIStackView, it stays at the default size of 1000x1000.
For now, I am working around this problem by adding the following code in viewDidLayoutSubviews:
if collectionView.bounds.size.width > view.bounds.size.width {
view.layoutIfNeeded()
}
Where collectionView is inside a UIStackView and view is the main view of the UIViewController. This allows all subsequent calculations based on the size of the view to be correct, and hopefully will not get called if the UIStackView behavior ever gets fixed.
Similar discussions here and here. Interesting point that in XCode8, the new default is to not save sizes of views in the XIB file, but instead bring everything in with an initial size of 1000x1000, to be resolved during the first layout pass. Except for UIStackViews, I guess.
Are you having sizeForItemAtIndexPath() return itemSize? I have found that implementing that function is the only reliable way to size a UICollectionViewCell dynamically at runtime.
I’ve taken a lot of time to read other questions before asking this one, and haven’t managed to solve the issue yet – hopefully someone can help.
I have a custom alert view, a UIView subclass. I’m trying to get it into a state where its width is fixed, and its height grows dynamically based on the height of its two labels (title and message), and a dynamic number of action buttons it can be set up with.
I’ve used AutoLayout to set up the controls, which is working fine, but for now I’m still manually setting the frame of the view itself in its initialiser.
What I need to do is have the view’s height be calculated by the height of my two labels, and n buttons, plus the spacing between them.
I’ve tried providing an intrinsicContentSize for my view, but that method is never called, and it doesn’t even ask for it, so I’m stumped as to how to proceed.
To summarise: I probably need iOS9’s UIStackView, but I don’t have that luxury, so I need to bake something similar into iOS 7 / 8. I’ve looked at OAStackView but I’d rather not use an external dependency.
Just a quick thought: Have you tried to create the view in Interface Builder? Give it a fixed width with Priority: 1000. Add no Height constraint but center the view vertically in its superview.
Then set the View's vertical content hugging priority and vertical content compression resistance priority to 1000.
This should cause the view to calculate its own height based on its subviews. You only need to make sure, that the subviews are bound to the superview in a way that defines the height without any ambiguity.
I have a weird output with autoLayout used for following scenario.
I have 4 sub pages to scroll. (subPage or scrollPage has been designed separately with autoLayout).
MainView has a scrollView component which loads the sub pages.
Everything is fine except the starting. After first load, the sub page components are not arranged properly. As soon as it receives a first tap/touch, automatically re-scrolls/re-arranges to proper places. which looks like a bug.
The loading creates problem. I have attached 2 images for reference for the above scenarios.
FirstOne at the first loading
Second one just after tapping on scroll area
Second one is the proper one. I need to show this instead of the first.
Need help to fix this.
Thanks,
Satyaranjan
One more thing to note,
[myScroll scrollRectToVisible:CGRectMake(320*pageNumber, 0, 320 , 240) animated:NO];
this is not working As I am using dynamic width for the subPage. Because it will vary for iPhon5 and iPhone6
First, you generally don't want to set the scrollView's contentSize when using autolayout -- if your sub views are laid out correctly, it will do that automatically.
Try this:
constrain all four of scrollView's edges to its parent
make sure translatesAutoresizingMaskIntoConstraints is NO for all views
Create a view called "contentView" and parent it to the scrollView.
pin all four of contentViews's edges to scrollView
parent your subviews to contentView and arrange them with autolayout
If for some reason you need to change the size of a subview programmaticly, you need to override its intrinsicContentSize method to return the correct size after the change. After the change, you might need to call the view's sizeToFit method from its parent view (not sure about that -- it may happen automatically).
In general, when using autolayout, you should almost never explicitly set the size of anything. If it can't be avoided, you should do it by creating height/width constraints and modifying them at runtime in the updateConstraints method.
EDIT:
I made an example project which demonstrates how to set up a scrollView and a couple other things.
Take a look!
https://github.com/annabd351/AutolayoutTemplate
Hello there fellow iOS programmers. While creating an app I've ran into a problem I can't seem to find an answer to. Let's lay it out:
I'm creating a UIViewController with a UIScrollView as it's only child. Inside this view I have a UIView, and inside of this there is a list of UIViews with UILabels inside them. As you all know you need to specify a contentSize for a UIScrollView.
The problem is that the list needs to be dynamic with it's content, and I therefore have no way to know the views heights beforehand. I'm adding all views with constraints where the height is set to ">=0".
When I later try to set the height of the UIScrollView I need to either get the height of the UIView that the list is inside, or get the origin.y and height of the last view in the list. This of course needs to be ready by the time the view is displayed to the user.
I've currently tried view.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize), which returned 0; view.systemLayoutSizeFittingSize(UILayoutFittingExpandedSize), which returned 10000; and view.origin.y + view.frame.height, which also returns 0. It seems to me like the constraints haven't taken effect yet.
I've tried setting both constraints and UIScollView.contentSize from viewDidLoad(). I've also tried to set constraints in viewDidLoad and setting contentSize in viewWillAppear; this yielded the same results. Setting constraints in viewWillAppear and setting contentSize in viewDidLoad only caused a crash.
Bottom-line: When should I set up the UIScrollView.contentSize if I want to get view.height or similar methods to return a correct result, while at the same time be ready by the time the user sees the view?
Btw, I'm making this app in Swift, so answers in this language is preferred, but I'm able to translate from Objective-C to Swift; post in whatever suits you best.
Thank you! :)
You say:
As you all know you need to specify a contentSize for a UIScrollView.
No, as TN2154 says, the constraints between the scroll view and its subviews are "interpreted as the content size of the scroll view" (emphasis added). This is a boon, because you no longer have to mess around with contentSize if doing auto-layout. Just set the constraints for the scroll view's subviews and the content size takes care of itself. This leverages the labels' intrinsic size (and make sure that the label's numberOfLines to zero).
The only trick is that it sometimes cannot figure out the correct width of the labels (because the trailing constraint is to the scroll view's content size, it will sometimes make the scroll view a horizontally scrolling one). You can remedy this by either manually setting preferredMaxLayoutWidth or by setting a width constraint between the label and the scroll view's superview.
Personally, while I understand the inclination to add the UIView containers between the scroll view and the labels, I'd suggest losing them unless you need them for some other reason. IMHO, it simply complicates the constraints unnecessarily. It's hairy enough as it is. Obviously, if these containers bear other utility for you, then go ahead and keep them (and they'll work fine), but if you're doing this simply for the sake of the constraints, you might consider eliminating them.
I have been struggling with IB and auto layout and figure there must be an easier way to create a simple scrollable form with multiple fields on it. The width can adjust to the device screen width (or in the case of the iPad to the details view of a split view.
The basic layout is simple a label and underneath a field (in some cases the field must have a minimum height) repeated for each attribute.
I think I must be doing something completely stupid or IB's autolayout is hopeless, I suspect the former. In any event whatever I do in IB fields just adopt whatever constraints they feel like it seems. Even when I set a constrain it seem IB just replaces it sometimes.
Currently I am using a view with a fixed height and width that fits the screen and laying out fields on this form, however its a complete nightmare having to pin every field in almost every direction. And then IB complains about a million constraints that it can't satisfy.
Hopefully someone has a better way of doing this they are willing to share - perhaps it might be best just to write some code to set up the constraints rather than relying on IB ?
Thanks
You are right UIScrollView and autolayout is not clear at first look.
You have to understand that when you are adding layout constraints for a view inside scroll view, the superview is content view of scroll (not the view of the UIScrollView) that can be more or less then current view of the ScrollView in IB.