How to automatically have content size increase in UIScrollView on an action with autolayout? - ios

I've setup a test project (project here) for this question to better illustrate my question, but here is the core issue I'm facing:
I have a UILabel nested within a content UIView which is itself nested inside a UIScrollView, and this is all laid out with AutoLayout. My initial layout works great, and the label is properly truncated to fit within it's content view as desired by the initial layout constraints. All is good so far.
Now the issue is when I want to add an "expand" action on the label. Let's say any tap on the content view should expand the label such that there is no longer truncation, and instead of truncating inside the contentView's bounds I want the scrollView's contentSize to increase such that the full label's text can now be scrolled. How should I accomplish this?
In the sample project, a tap on the content view will adjust the entire scrollView's constraints just to illustrate that the label now has a different amount of space to work with. I'm stuck as to how to now have the scrollView recalculate it's contentSize such that the label no longer truncates.
I've tried playing with content compression priority, and changing that based on the action being done, like so:
theLabel.setContentCompressionResistancePriority(UILayoutPriorityRequired, forAxis: .Vertical)
But this doesn't seem to actually enforce that the label isn't going to truncate. I've also tried invalidating the intrinsic content size of the scrollView on the tap action, but to no avail. How do I toggle whether or not the label should truncate?

Generally you have a constraint which pins the height of the label or the view to your minimum height and when you want to expand you disable it (make it inactive) and layout the view. To compress re-enable the constraint and layout the view.

Related

Calculating contentSize for UIScrollView when using Auto Layout

Here is a quick question about something which works, but could be written much better. I have a UIScrollView and a list of objects laid out inside, one under the other. Everything is done during viewDidLoad() and the placement of the objects uses Auto Layout. Here is what I do to set the contentSize height of the UIScrollView to its appropriate value.
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
globalView.contentSize = CGSize(width: globalView.frame.width,
height: globalView.frame.height * 1.59)
}
It works, but the arbitrary value 1.59 has obviously been decided by me trying a few possible values. What is the proper way to compute the contentSize in such a case? I am doing everything programmatically.
Searching the net didn't lead me to any simple and clear answer, so eventhough it may be a somewhat duplicate question I decided to reformulate it.
Giving content size programatically is not good way. The below solution which will
work using autolayout, dont need to set content size at all. It will calculate as per
how many UI fields added to view.
Step 1 :
Add Scrollview to view in storyboard and add leading, trailing, top and bottom constraints
(All values are zero).
Step 2 :
Don't add directly views which you need on directly scrollview, First add one view
to scrollview (that will be our content view for all UI elements).
Add below constraints to this view.
1) Leading, trailing, top and bottom constraints
(All values are zero).
2) Add equal height, equal width to Main view (i.e. which contains scrollview).
For equal height set priority to low. (This is the important step for setting content size).
3) Height of this content view will be according to the number of views added to the view.
let say if you added last view is one label and his Y position is 420 and height
is 20 then your content view will be 440.
Step 3 : Add constraints to all of views which you added within content view as per your requirement.
For reference :
I hope this will definitely help you.
Though I finally got to solve this issue and make things work.
I noticed that all I could find on the net as related answers was directed to storyboard users. But nothing to do it programmatically that I could put my hands on.
So I decided to make an extremely simple demo app to show how this can be achieved. I hope it will be useful to someone at some point.
Here is the address to get it on GitHub:
https://github.com/zaxonus/AutoLayScroll
I also used constraints programmatically to dynamically modify UIScrollView's contentSize . I pretty much followed Shrikant's instructions but for step 2.1, I set centerX to the scrollViews.centerX rather than set the leading and trailing margins (No idea why the former works, while the latter doesn't). Then as a last step, after adding the final subview, I did the following:
contentView.layoutIfNeeded() //set a frame based on constraints
scrollView.contentSize = CGSize(width: contentView.frame.width, height: contentView.frame.height)
Hope this helps somebody in the future.
All you need to do is to make sure that the first subview's top anchor is constrained to the scrollView's top anchor and the last subview's bottom anchor is constrained to the scrollView's bottom anchor
If you don't want to use storyboards, and just use constraints, you can follow the guide here
https://redflowerinc.com/implement-uiscrollview-using-constraints-no-need-to-use-content-size/
Use can use bottom anchor and pin your views to the scroll view. By using constraints you don't need to use content size.
This is the official guide from Apple, and works for me.
https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/AutolayoutPG/WorkingwithScrollViews.html
To support scroll views, the system interprets constraints differently, depending on where the constraints are located.
Any constraints between the scroll view and objects outside the scroll view attach to the scroll view’s frame, just as with any other view.
For constraints between the scroll view and its content, the behavior varies depending on the attributes being constrained:
Constraints between the edges or margins of the scroll view and its content attach to the scroll view’s content area.
Constraints between the height, width, or centers attach to the scroll view’s frame.
You can also use constraints between the scroll view’s content and objects outside the scroll view to provide a fixed position for the scroll view’s content, making that content appear to float over the scroll view.
For most common layout tasks, the logic becomes far easier if you use a dummy view or layout group to contain the scroll view’s content. When working in Interface Builder, the general approach is shown below:
Add the scroll view to the scene.
Draw constraints to define the scroll view’s size and position, as normal.
Add a view to the scroll view. Set the view’s Xcode specific label to Content View.
Pin the content view’s top, bottom, leading, and trailing edges to the scroll view’s corresponding edges. The content view now defines the scroll view’s content area.
The content view does not have a fixed size at this point. It can stretch and grow to fit any views and controls you place inside it.
(Optional) To disable horizontal scrolling, set the content view’s width equal to the scroll view’s width. The content view now fills the scroll view horizontally.
(Optional) To disable vertical scrolling, set the content view’s height equal to the scroll view’s height. The content view now fills the scroll view horizontally.
Lay out the scroll view’s content inside the content view. Use constraints to position the content inside the content view as normal.
IMPORTANT
Your layout must fully define the size of the content view (except where defined in steps 5 and 6). To set the height based on the intrinsic size of your content, you must have an unbroken chain of constraints and views stretching from the content view’s top edge to its bottom edge. Similarly, to set the width, you must have an unbroken chain of constraints and views from the content view’s leading edge to its trailing edge.
If your content does not have an intrinsic content size, you must add the appropriate size constraints, either to the content view or to the content.
When the content view is taller than the scroll view, the scroll view enables vertical scrolling. When the content view is wider than the scroll view, the scroll view enables horizontal scrolling. Otherwise, scrolling is disabled by default.

UISlider causing horizontal scrolling

I have a simple screen, with a slider and a label positioned next to each other horizontally. I have embedded these inside a UIScrollView (I set this to fill the screen and used 'Add missing constraints'), because I will need vertical scrolling later down the line. I don't however, want horizontal scrolling. I have seen numerous posts on here and other sources about people wanting to disable horizontal scrolling, however I'm not sure that's what I want to do, I think I need to restrict the UISlider from causing the horizontal scrolling; I think it is trying to take up more width than the screen. I have added what I think are the necessary horizontal constraints:
Leading space to container for the UISlider
Horizontal spacing to the UILabel, and
Trailing space to container for the UILabel
But this still causes horizontal scrolling, and the UISlider's are the cause, they are taking up more room than I want, as seen below:
I have tried disabling horizontal scrolling in the code using a few techniques, one being:
func scrollViewDidScroll(scrollView: UIScrollView) {
if scrollView.contentOffset.x>0 {
scrollView.contentOffset.x = 0
}
}
but this does not seem to stop the horizontal scrolling.
Can anyone offer any suggestions?
Thanks in advance.
My suggestion is to never use Add missing constraints. It never does what you really want.
Here's the problem. You are laying out your UI on a ViewController in the Storyboard that is square. Apple did this to remind you that you need to be flexible in your design, but it doesn't match the size of any device. When you Add missing constraints, it uses the absolute dimensions of that square to create the constraints which are certainly wrong.
In your specific case, it is giving the slider a width that is too wide, which is why the slider goes off the right side of your screen.
Here's the trick about scroll views. If the contents inside of a scroll view are wider than the scroll view itself, then that content will scroll. The same applies vertically: if the contents inside of a scroll view are taller than the scroll view, then the contents will scroll.
In order to design this to work on all phones, you need to make sure that the contents of the scroll view are laid out correctly for each phone size. Which certainly means you don't want to use specific widths for both the label and the slider because you'll end up with the wrong width for some device, if not all of them.
The best way to do this is to:
Drag out the scroll view and add it to your ViewController. Add constraints to make sure it is properly sized on all phones, such as attaching it on all sides to its superview with a fixed distance.
Drag out a new UIView and drop it on the scroll view. Drag its edges until it exactly matches the size of the scroll view. This will be your content view. Pin all four edges of this content view to the scroll view with offsets of 0.
Here's a tricky bit. Even though you've pinned the content view to the scroll view, its size of free to grow because that is what allows it to be bigger than the scroll view itself and allow there to be content to scroll over. To keep your scroll view from scrolling horizontally, you need to make sure the content view has the same width as the scroll view on all devices. To do that, find the scroll view and the content view in the Document Outline to the left of the Storyboard. Control-drag from the content view to the scroll view and select Equal Widths from the pop-up.
You still haven't told your content view how tall it should be. For now, give it an explicit height constraint of 1000. That will be enough to scroll.
Now, add your label and slider to the content view. In addition to constraining them to each other and to the edges of the content view, you will need to give your label a width constraint. Then Auto Layout will have all of the information it needs to compute the width of your slider. Auto Layout knows how wide the content view is (which will be different on different devices), it knows how wide your label is, and how far everything is from everything else, so it will just stretch the slider to fill in the rest.
If you do all of this, you will have a UI that is properly sized for all devices in all orientations that scrolls vertically.
Just embed all view in your UIScrollView in a UIView, give it the required constraints then the slider and label will stay.
That worked for me just now.
UIScrollView is special when you want use AutoLayout with it, subviews can not be added directly, it needs a container view to constraint the contentSize of UIScrollView, Auto Layout Guide:Working with Scroll Views explains the detail reason, and you can find many solutions to solve UIScrollView's auto layout on Google, Such as this answer.
To be honest, it's confused and complicated to understand UIScrollView's auto layout, but if you overcome this, others auto layout question is easy to resolve.

How to change uiview's height using auto layout?

So I created this scenario in order to understand how views increase in height according to their content. However am still not able to make it happen.
This is what I have now:
the textview is growing according to content. however the uiview containing it is disappearing. what constraints should I use so that when the uitextview becomes bigger, its parent view also increase in height?
Start by clearing all your constraints from everything so we have a fresh slate.
Step 1: Build your view hierarchy. In this example, we want something like this:
The view controller's view, which we'll call parentView has a subview, which we'll call redView. That redView has a child, a Text Field, which we'll call textField.
The hierarchy looks like this:
Step 2: Set any constraints that are specific to any individual view. In this case, we probably only want to set a width constraint on our text view. For now, I'll just set a width of 200pts.
Step 3: Set the constraints between textView and its parent, redView. Let's say we want a 10pt border all the way around. Let's add these constraints:
Once we've added these constraints we'll gets some auto layout warnings and errors. For starters, because the constraints I added for with and space to superview don't match the actual sizes, I'll get some warnings like this:
There will also be some errors describing missing X and Y positions for redView and for textView. There are really twice as many errors here as necessary. textView knows where to position itself relative to redView. We don't need more constraints to sort out textView's position. However, redView doesn't know where to position itself yet... and so ultimately, textView also sort of doesn't exactly know.
We can update the textView's frame to get rid of the warnings, but let's go ahead and fix the actual errors.
Step 5: Set up redView's constraints relative to the superView. redView already know what size to be. Notice we had no errors for redView's width. It just doesn't know where to be. In this case, I'll go simple and say we want redView to be centered. So we'll want to add these constraints:
Now we've fixed some of the problems. The only problem that remains is the height for everything.
To fix this, we must set the content sizing priorities of textView. Set these all to 1000 and change the "Intrinsic Size" property to "Placeholder".
By now, all of the auto layout errors should be gone and we should only be left with warnings because our storyboard frames don't match what our constraints say they should.
We can fix that by selecting parentView and updating all the frames:
There's one final caveat to this auto layout puzzle when it comes to autosizing based on content size: what happens if our text view has no content?
If our textview has no content, auto layout will choose a height of 0, and our users won't even be able to see that there's a text view there, much less tap in it to add content (and make it expand). When using auto layout and content-based sizing, we should almost always be sure that we've set either an explicit or minimum size for the content view.
We don't have to worry about our textView's width, as we set this explicitly to 200. So let's add a minimum height constraint. Start by adding any height constraint:
Now go to the size inspector for the textView, find this height constraint we added, and edit it into a greater than or equal to constraint:
Storyboard won't reflect the change in content in our textView and resize it appropriately, but your constraints are now set up correctly and this will behave appropriately on your device or in the simulator.
ON UITextView make your you unselected These
Scrolling Enabled
Bounces
Bounce Horizontally
Bounce Vertically
Shows Horizontal Indicator
Shows vertical indicator
Now Change your auto layout constraints like this.
On the Storyboard Page, click on your textview.
Then click on the small triangular in the lower right corner.
Click first on "Clear Constraints".
An then on "Add Missing Constraints".
Its the easiest way.

Using autolayout to manage height of UILabel within a subview

Using Xcode 5, interface builder and developing for iOS 7.
Within my content view I have 2 additional sub views, one on top of another. Within the upper subview I have a UILabel. I would like for that UILabel to expand in height when the content exceeds the first line, but I can't seem to get the height increase of the UILabel to increase the height of the subview, thus pushing the bottom subview down the main content view.
Additionally, I would assume the content view would need some sort of a constraint that reflects the overall height of the two subviews?
Perhaps this the question has already been answered somewhere, but I've searched everywhere and can't seem to come up with a solution.
Any help would be hugely appreciated! Thanks in advance.
There is a couple of steps that you have to do to achieve this using autolayout.
Set layout constrains for the label.
Set height constraint with low priority.
Set numberOfLines to 0 to allow multiline text.
Set preferredMaxLayoutWidth for the label.
The preferredMaxLayoutWidth is used by label to calculate its height.
This property affects the size of the label when layout constraints
are applied to it. During layout, if the text extends beyond the width
specified by this property, the additional text is flowed to one or
more new lines, thereby increasing the height of the label.
Also, have a look here.

Resizing a UITextView in UIScrollView using Auto Layout issue

I really hate to ask here because I usually try to figure things out on my own. But on this one I've stuck for days and can't find a solution anywhere online.
I have a ScrollView containing multiple subviews. I've got an image view and two labels at the top with fixed heights. Then there is a UITextView and another ImageView (see pictures).
I add the text to the text view programmatically so it should have a dynamic height and the ImageView should move to the bottom so you can scroll. I don't want the TextView to be scrollable in itself but I want all the subviews to move as well.
I know I should be able to solve this issue using constraints. But I feel like I've tried everything and nothing worked yet. It worked when I disabled auto layout and moved the views manually. I'm wondering if there is a better way though.
As you can see I pinned the TextView to the ImageView above with a 1,000 priority and to the ImageView below with a 1,000 priority. The height constraint can not be deleted so I set it to the lowest possible priority. The ImageView on the bottom is pinned to the bottom of the superview with an absolute height. Its height constraint also has low priority. (I can post an image of the ImageView's constraints, if it helps)
I also tried adapting the frame programmatically but this is not working well in combination with auto layout. (If it helps I can of course post the code)
What am I doing wrong? Shall I just disable auto layout and do it manually? This seems unclean to me. Is it even possible to do?
I really appreciate your help :)
Greets,
Jan
Make sure the Scrolling Enabled attribute on the UITextView is unchecked in Interface Builder. I believe that the Auto Layout system takes that into account when it calculates the intrinsic content size.
If somebody is struggling with a similar problem: This is what I ended up doing:
Remove all subviews from the ScrollView in IB
Programmatically add a single UIView to the ScrollView.
Add all the views to the UIView as subviews (move them using setFrame)
Set the Frame of the UIView appropriately to the subviews
Set the ScrollView's contentSize to the size of the UIView.
A little more work but it finally works. This follows Apple's mixed approach guidelines that can be seen here (look for UIScrollView): http://developer.apple.com/library/ios/#releasenotes/General/RN-iOSSDK-6_0/index.html
The problem is the height setting. You somehow have to try to delete it. If you have added other constraints that are "sufficient", it should become deletable.
At the moment you have one user constraint for the height that is "Greater or equal" and an "Equals" constraint as well. Clearly, those are not working well together.
Maybe there is a conceptual error as well. The lower image view should not be fixed in position, so the distance to the lower image view will not be a "sufficient" constraint to let you delete the fixed height.
I think it should work if
the lower image view has a fixed height and
a fixed distance to the text view above, and
the text view has a minimum height as well as
a fixed distance to the image view above
(which should be fixed in relation to the superview).

Resources