Content offset of UIScrollView being set when `resignFirstResponder()` is called on a nested UITextField

Update

It appears that the content offset is only set as a consequence of calling UITextField.resignFirstResponder() when the main scrollview is scrolled by a certain amount. In other words, there's a threshold. If the content offset is underneath the threshold calling resignFirstResponder() won't result in the content offset being set, and if above it, then it will be set to it. In other words it appears that UIKit has a problem with the content offset being higher than X, and if it is at the time the keyboard is dismissed, then it is set to X.

Original

I have a UIScrollView that is normally the full width and height of the screen. It is constrained to the top anchor of the keyboard layout guide. So when the keyboard pops up, this scrollview's frame shrinks by the height of the keyboard.

Inside this scrollview is another scrollview. It contains a stackview that hosts the UITextFields. The fields are scrollable, and the main scrollview is only scrollable when the keyboard is displayed.

The problem occurs when the keyboard is being dismissed. I'm trying to access the content offset of the main scrollview at the time keyboard begins to hide. I do this using keyboard will hide/show notifications. However, at this point in time the content offset has been set to something different than it was at the time the user dismissed the keyboard.

I narrowed down the problem to the UITextField.resignFirstResponder() method. I overrode the contentOffset property of the main scrollview and found using breakpoints that when resignFirstResponder() is called on the focused textfield, the content offset of the scrollview is changed by UIKit.

What I want is for UIKit to stop changing the content offset when UITextField.resignFirstResponder() is called. This way I can get the content offset at the time the user dismissed the keyboard from within the keyboard will hide callback.

I'll create a minimal reproducible example if really required, but I thought I'd take a shot and see if this issue rings a bell for anyone that already knows a solution to it.

Replies

Update

Okay, so I know exactly what that threshold amount is equal to. It is equal to the content offset when the bottom of the content is resting at the bottom of the scrollview. In other words, the content offset that results in the content being scrolled all the way to the bottom. It appears that this threshold is used because what is an acceptable content offset when the keyboard is up (smaller scrollview bounds) is not an acceptable offset when the scrollview's bounds increase (keyboard goes down, scrollview gets bigger). For example, if the content is 100 points high and the bounds are 50 high, then it makes sense to have an offset that is 50, because you can only view 50 points of the content at any given time. However, if the bounds were to increase to 100, then a 50 point offset is somewhat erroneous because at that height all of the content is visible at 0 offset, so increasing the offset at all — assuming no insets — just pushes content needlessly out of view.

This makes sense, but I think the problem here is that I wasn't expecting this content offset change to occur in the current run loop. In my experience with layout changes that are the result of AutoLayout, they only seem to take effect in the next run loop. But here they are happening immediately — as soon as UITextField.resignFirstResponder() is called. This prevents me from being able to make decisions based on the content offset of the scrollview at the time of the keyboard being dismissed because the will hide keyboard notifications occur after those effects take place.

The solution I'm moving towards here is just not using the keyboard layout guide altogether, and instead just changing the bounds manually as I did before using the keyboard notifications; at least then I'll have access to the content offset representative in the UI at the time the notification is published.