UIScrollView Only Works After Arbitrary App Interaction - ios

I understand this is a duplicate of this question, but the question seems abandoned, the asker is not providing feedback and the current answers have not worked for me.
I currently have a simple app whose main view contains a UIScrollView. When the app is first installed and run, there is no scroll behaviour and the view is stuck at the top of the content view, which is not the intended functionality. Interacting with the app by rotating the device, touching in a text field or touching a segmented control all correct the scroll functionality (i.e. give the ability to scroll to the bottom of the content view):
Why is this behaviour happening?
How can it be fixed?
Details:
Once the app has been interacted with, it can be put in the background and reopened without losing the scroll functionality. Manually terminating the app from the background and reopening it will re-trigger the problem. The end goal is to have the scroll functionality work correctly and immediately without the user having to perform an arbitrary ritual to unlock part of the app's functionality (Very strongly discouraged in Apple's HMI guidelines).
The UIScrollView (scrollView) in question is embedded in the topmost UIView, and it has a UIView (scrollContentView) as a child. The scrollView's contentSize.height is defined programmatically in my viewWillLayoutSubviews() function, and this is the height that is used by the UIScrollView once one of the aforementioned actions is taken:
override func viewWillLayoutSubviews()
{
super.viewWillLayoutSubviews();
// (This is just setting a child table of the scrollView's height)
// Set the goal table's height to the height of its contents
formulaTableHeightConstaint.constant = getGoalTableHeight();
scrollView.contentSize.height = getGoalTableHeight() + Constants.formHeight;
}
At the end of the viewWillLayoutSubviews() function, if I print the scrollView.contentSize.height, it shows a large value around 2000, and printing the scrollView.frame.size.height shows a value around 600.
Attempts:
I have tried to use scrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: SomeArbitraryValues, right: 0). This allows scrolling immediately when the app is first opened, but the behaviour is unintuitive in that, even setting the bottom to values that should be smaller than the scroll content height, allows the app to scroll a good distance past the bottom of the content. Just the same, setting smaller values usually prevents the view from scrolling to the bottom of the content. From what I understand from the documentation, this seems like a slightly unrelated (and maybe smelly) property to use to fix this problem (Though I could be wrong). As well, even though messing with the contentInset seems to trigger correct scrolling behaviour, printing the contentInset value before and after the scroll works shows no changes to it occurred.
I have also tried adding a height constraint to the scrollContentView and setting that height programmatically to no avail.
Any help on the matter is greatly appreciated.

You should set the contentSize height as follows:
scrollView.contentSize = CGSizeMake(width, getGoalTableHeight() + Constants.formHeight);
scrollView.contentSize.height refers to the height member of the struct that is returned from the contentSize method.

Related

How to fit a "freeform" UIViewController to multiple screen sizes?

I have created several freeform UIViewControllers that have extra long heights for scrolling purposes, and widths that equal the width of an iPhone 8 Plus screen (because that's the physical device I have available for testing with).
My problem comes when I am trying to make my app functional on all devices down to an iPhone SE sized screen. The height isn't an issue, but the width is creating a problem because on smaller screen sizes.
UIViewController is obviously wider than the screen. so, it allows for horizontal scrolling/panning. However, my goal is to have the ViewController match the width of the screen being used. so, only vertical scrolling is possible.
I've tried to adjust the width of the UIViewController when it gets presented, but that seems to have no effect. I'm really not sure what else to try and can't find anything about this online. Is this an obvious fix or am I doing something completely wrong?
To properly answer this question and for other people that might be having this issue.
The view controller size automatically adjusts to the screen size (provided its properly configured).
If you have an scrollview inside a view controller, then you should make sure the edges of the scrollview are attach to its parent view (the view controller's root view) using auto layout constraints.
In addition, the scrollview contents must be set to use the root view as the position reference (at least horizontally) otherwise auto layout won't know how to handle the horizontal positioning.
To accomplish this you just need to attach the inner view (that fills your entire scrollview) to the left and right edge of the view controller's root view as well.
I think you might be mistaking your UIViewController with a view. If your view is a scroll view, you can set the content size of the scroll view to be the same as the width of your device and it should achieve the desired effect.
In Swift 3.0 and above:
let screenWidth = UIScreen.main.bounds.width
yourScrollView.contentSize.width = screenWidth
To support multiple widths you can use different size classes available in iOS.
there are many tutorials available for size-class related constraints.
like https://www.bignerdranch.com/blog/designing-for-size-classes-in-ios/,
https://www.bignerdranch.com/blog/designing-for-size-classes-in-ios/
you should have to give View's constraints in relation to their Parent views if possible for your design.
I've tried to adjust the width of the UIViewController when it gets presented
By "presenting" you mean viewDidLoad method? I don't think it's the best place to adjust width because at this moment view bounds are not yet known. Besides viewDidLoad method is not called when devices is rotated.
I suppose you should update scroll view width in viewDidLayoutSubviews which is called every time when root view bounds changed in view controller:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let screenWidth = UIScreen.main.bounds.width
yourScrollView.contentSize.width = screenWidth
// I don't think you really need it
// but it may help
yourScrollView.setNeedsLayout()
}

ContentSize Height of UIScrollView in iPhone X not updating

This issue sounds easy at first but this is one of those things that I had to ask a question for suggestions since there is no other way I can think of to solve this. I already searched too far for solutions but did not find any.
Situation
I have a collection view that scrolls horizontally that covers the whole screen.
I added a scroll view inside each cell's content view that contains several views that will be taller than screen height.
After the last subview is added I update scroll view content size using:
scrollview.contentSize = CGSize.init(width: screenWidth, height: (scrollview.subviews.last?.frame.origin.y)! + (scrollview.subviews.last?.frame.size.height)!)
Let's say "scrollview" is the scroll view and "screenWidth" is the width of the screen. It works on other devices except iPhone X will scroll maybe for two screen heights worth but that's it. Other devices can go to the bottom of the scroll view.
While writing this I thought:
what if I need to add top and bottom padding of safe area to content size height since it extends there.
I did it but it only added so little and a big part of views still cannot be seen due to this issue.
Inputs are welcomed. Thanks in advance.

WKWebView+contentInset makes content size wrong

On iOS, when you set a contentInset on a WKWebView's scroll view, it seems to make the web view think the content of the page is bigger than it is. For example, if you give a mostly-empty page a top or bottom contentInset, you'll be able to scroll the page down even though there's nothing to scroll to.
Is this expected behavior? Is there a workaround that still allows me to use contentInset?
The problem is that WKWebView always seems to use its frame as its viewport size, neglecting to subtract the scrollView's contentInset. I was able to find a workaround by stephan-leroux.
The idea is to set the WKWebView's frame to the desired viewport size, either manually or using autolayout constraints. To restore the "scroll-under" effect of contentInset, disable the scrollView's masksToBounds:
wkWebview.scrollView.layer.masksToBounds = NO;
weiyin's suggestion (resizing the web view's frame) worked for me.
Just wanted to share a code example:
CGFloat topInset = ...;
web.scrollView.layer.masksToBounds = NO;
[web.scrollView setContentInset:UIEdgeInsetsMake(topInset, 0, 0, 0)];
[web setFrame:CGRectMake(0, 0, web.width, web.height-topInset)];
Four years later and the problem still persists...
weiyin's answer works great, but if you're like me you might struggle a bit with figuring out what exactly to do (though it's really quite simple).
So here's a simple explanation for dummies (like me):
Embed your Web View inside a Wrapper View.
Size and position your Web View in a way that its frame is inset from the Wrapper View precisely by your desired content inset.
Set clipsToBounds = false on the Web View's scroll view (and true on the Wrapper View).
Make sure that there is no content inset set on the Web View (webview.contentInset = .zero).
(This is the default and normally, you don't need to set it at all).
Done:
100% content inset behavior on the Web View without using content insets.

uiscrollview--do I understand this correct?

ok, I have spent hours and hours trying to figure this out. read countless other questions on here, which have helped me understand how UI scrollview works, but which have not helped me actually implement one into my app. I think I finally understand--but it just doesnt seem correct. Can someone please tell me where I am going wrong (if I am) in the process below:
Have new, empty view controller.
add scrollview to view controller.
Put constraints on scrollview: Pin 0, 0, 0 and 0 to the view.
insert new UIview inside the scrollview. rename is contentView
put constraints on contentView: 0, 0, 0 and 0, all against the scrollview.
Now here is the tricky part: uiscrollview doesnt have a specific size--it only takes on the size of the container (contentView) that is inside it. So unless you give the contentView a certain height and width, then it says "scrollview has ambiguous scrollable height/width."
OK, so that makes sense. So I want the scrollable content height to be 900, so I set the height of the contentView to 900. But, I do not want horizontal scrolling. Users don't like it and it makes for an ugly user interface. But, the problem is that I have to set the contentView to a certain width, or else the width is 0 and no content shows up inside it.
So, I choose a certain width, for example 320. This works great for the iPhone 4S and 5/5S. And if the 4.7" and 5.5" iphone's didnt exist, this would be fine. But it gets a little awkward for the larger iphone's, because then the the content on the scrollview can't fully go across the screen, it abruptly ends and doesn't look presentable.
So, is there any way around this? I have been just using a tableview instead in the past, but now I need access to something that the uitablecell class can't do, and that's not an option. I tried setting the contentView to equal widths of the Superview, and that didnt work either, i still recieved the message "scrollview has ambiguous scrollable width." I just want the width of the contentView to be 320 for iphone4/5, and for it to be 400 for the 6/6 plus. Is this possible?
Constrain the scroll view's content view to be the same width as the root view (i.e. the one that contains the scroll view). Absolutely do not hard-code widths.
EDIT: Finally! Here's a screenshot of the storyboard I just created. I can put the project code on GitHub if anyone wants it.

iOS: Getting height of views with programatically added constraints as only indicator

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.

Resources