I have a ViewController with the following structure ((x)
indicating the level):
UIViewController (1)
- NavigationBar (2)
- UIScrollView (2)
- UIView (3)
- UITextField (4)
- UITextField (4)
- UITextField (4)
- UITextField (4)
- UIButton (4)
4
are vertically constrained to eachother with a spacing of 16.4
are constrained to the UIView's (3
) top and bottom.3
) is constrained with top and bottom to the UIScrollView (2
).2
) is constrained to the NavigationBar's bottom (2
) and the superview's bottom (1
)The UIView (3
) has the following constraints:
In the viewDidLoad
of the viewController, I call this:
registerForKeyboardWillShowNotification(self.scrollView)
registerForKeyboardWillHideNotification(self.scrollView)
Where registerForKeyboard...ShowNotification
is an extension of UIViewController
:
extension UIViewController
{
/// Act when keyboard is shown, by adding contentInsets to the scrollView.
func registerForKeyboardWillShowNotification(_ scrollView: UIScrollView, usingBlock block: ((CGSize?) -> Void)? = nil)
{
_ = NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillShowNotification,
object: nil, queue: nil)
{ notification in
let userInfo = notification.userInfo!
let keyboardSize = (userInfo[UIResponder.keyboardFrameEndUserInfoKey]! as AnyObject).cgRectValue.size
let contentInsets = UIEdgeInsets(top: scrollView.contentInset.top,
left: scrollView.contentInset.left,
bottom: keyboardSize.height,
right: scrollView.contentInset.right)
scrollView.contentInset = contentInsets
block?(keyboardSize)
}
}
/// Act when keyboard is hidden, by removing contentInsets from the scrollView.
func registerForKeyboardWillHideNotification(_ scrollView: UIScrollView, usingBlock block: ((CGSize?) -> Void)? = nil)
{
_ = NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillHideNotification,
object: nil, queue: nil)
{ notification in
let userInfo = notification.userInfo!
let keyboardSize = (userInfo[UIResponder.keyboardFrameEndUserInfoKey]! as AnyObject).cgRectValue.size
let contentInsets = UIEdgeInsets(top: scrollView.contentInset.top,
left: scrollView.contentInset.left,
bottom: 0,
right: scrollView.contentInset.right)
scrollView.contentInset = contentInsets
block?(keyboardSize)
}
}
}
However, when the keyboard shows, it doesn't inset the scrollView (enough). I debugged, and this is the case:
height = 216
height = 260
height = 291
I first though the suggestion bar could be the problem, but it's not.
In registerForKeyboardWillShowNotification
I changed bottom: keyboardSize.height
to bottom: keyboardSize.height + 30
, which gives exactly the same result (I see the same part of the button that's partially hidden behind the keyboard). Once I add 50 or more, it finally seems to make a small difference.
keyboardWillShowNotification
I tried keyboardDidShowNotification
, this doesn't make a difference.keyboardFrameEndUserInfoKey
I tried keyboardFrameBeginUserInfoKey
, this doesn't make a difference.What am I missing here?
Unfortunately I haven't been able to solve this, I'm unsure why this doesn't work as expected.
However, I got the expected behaviour by using a UIStackView inside the UIScrollView. I used this article as reference.
The UI layout
The UIScrollView
leading
and trailing
are constrained with 16 to the superView. top
is constrained with 0 to the navigationBar's bottom.bottom
is constrained with 0 to the superView's bottom.The UIStackView
leading
and trailing
are constrained with 0 to the scrollView.top
and bottom
are constrained with 24 to the scrollView, to get the desired spacing to the navigationBar, and between the button and the keyboard.axis=vertical
, alignment=fill
, distribution=fill
, spacing=24
.The NavigationBar
The NavigationBar is a custom class that derives its height from its contents. This didn't have a height constraint set, but a placeholder height of 100. The NavigationBar would fill the entire screen. This is solved by removing the placeholder height, and adding any height constraint with a low priority (in this case a priority of 1
).
The initial code for applying the keyboard inset works now.
/// Act when keyboard is shown, by adding contentInsets to the scrollView.
func registerForKeyboardWillShowNotification(_ scrollView: UIScrollView, usingBlock block: ((CGSize?) -> Void)? = nil)
{
_ = NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillShowNotification,
object: nil, queue: nil)
{ notification in
let userInfo = notification.userInfo!
let keyboardSize = (userInfo[UIResponder.keyboardFrameEndUserInfoKey]! as AnyObject).cgRectValue.size
let contentInsets = UIEdgeInsets(top: scrollView.contentInset.top,
left: scrollView.contentInset.left,
bottom: keyboardSize.height,
right: scrollView.contentInset.right)
scrollView.contentInset = contentInsets
block?(keyboardSize)
}
}