ScrollView and keyboard in Swift
Solution 1
In ViewDidLoad, register the notifications:
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name:UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name:UIResponder.keyboardWillHideNotification, object: nil)
Add below observer methods which does the automatic scrolling when keyboard appears.
@objc func keyboardWillShow(notification:NSNotification) {
guard let userInfo = notification.userInfo else { return }
var keyboardFrame:CGRect = (userInfo[UIResponder.keyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
keyboardFrame = self.view.convert(keyboardFrame, from: nil)
var contentInset:UIEdgeInsets = self.scrollView.contentInset
contentInset.bottom = keyboardFrame.size.height + 20
scrollView.contentInset = contentInset
}
@objc func keyboardWillHide(notification:NSNotification) {
let contentInset:UIEdgeInsets = UIEdgeInsets.zero
scrollView.contentInset = contentInset
}
Solution 2
The top answer for swift 3:
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name:NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name:NSNotification.Name.UIKeyboardWillHide, object: nil)
And then:
func keyboardWillShow(notification:NSNotification){
//give room at the bottom of the scroll view, so it doesn't cover up anything the user needs to tap
var userInfo = notification.userInfo!
var keyboardFrame:CGRect = (userInfo[UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
keyboardFrame = self.view.convert(keyboardFrame, from: nil)
var contentInset:UIEdgeInsets = self.theScrollView.contentInset
contentInset.bottom = keyboardFrame.size.height
theScrollView.contentInset = contentInset
}
func keyboardWillHide(notification:NSNotification){
let contentInset:UIEdgeInsets = UIEdgeInsets.zero
theScrollView.contentInset = contentInset
}
Solution 3
Here is a complete solution, utilizing guard and concise code. Plus correct code in keyboardWillHide
to only reset the bottom
to 0.
@IBOutlet private weak var scrollView: UIScrollView!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
registerNotifications()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
scrollView.contentInset.bottom = 0
}
private func registerNotifications() {
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
}
@objc private func keyboardWillShow(notification: NSNotification){
guard let keyboardFrame = notification.userInfo![UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return }
scrollView.contentInset.bottom = view.convert(keyboardFrame.cgRectValue, from: nil).size.height
}
@objc private func keyboardWillHide(notification: NSNotification){
scrollView.contentInset.bottom = 0
}
Solution 4
for Swift 4.0
In ViewDidLoad
// setup keyboard event
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
Add below observer methods which does the automatic scrolling when keyboard appears.
@objc func keyboardWillShow(notification:NSNotification){
var userInfo = notification.userInfo!
var keyboardFrame:CGRect = (userInfo[UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
keyboardFrame = self.view.convert(keyboardFrame, from: nil)
var contentInset:UIEdgeInsets = self.ui_scrollView.contentInset
contentInset.bottom = keyboardFrame.size.height
ui_scrollView.contentInset = contentInset
}
@objc func keyboardWillHide(notification:NSNotification){
let contentInset:UIEdgeInsets = UIEdgeInsets.zero
ui_scrollView.contentInset = contentInset
}
Solution 5
Swift 5 Only adjust ScrollView when TextField is hidden by keyboard (for multiple TextFields)
Add / Remove Observers:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
NotificationCenter.default.removeObserver(self)
}
Keep track of these values so you can return to your original position:
var scrollOffset : CGFloat = 0
var distance : CGFloat = 0
Adjust ScrollView contentOffset depending on TextField Location:
@objc func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
var safeArea = self.view.frame
safeArea.size.height += scrollView.contentOffset.y
safeArea.size.height -= keyboardSize.height + (UIScreen.main.bounds.height*0.04) // Adjust buffer to your liking
// determine which UIView was selected and if it is covered by keyboard
let activeField: UIView? = [textFieldA, textViewB, textFieldC].first { $0.isFirstResponder }
if let activeField = activeField {
if safeArea.contains(CGPoint(x: 0, y: activeField.frame.maxY)) {
print("No need to Scroll")
return
} else {
distance = activeField.frame.maxY - safeArea.size.height
scrollOffset = scrollView.contentOffset.y
self.scrollView.setContentOffset(CGPoint(x: 0, y: scrollOffset + distance), animated: true)
}
}
// prevent scrolling while typing
scrollView.isScrollEnabled = false
}
}
@objc func keyboardWillHide(notification: NSNotification) {
if distance == 0 {
return
}
// return to origin scrollOffset
self.scrollView.setContentOffset(CGPoint(x: 0, y: scrollOffset), animated: true)
scrollOffset = 0
distance = 0
scrollView.isScrollEnabled = true
}
Make sure to use [UIResponder.keyboardFrameEndUserInfoKey] to get the proper keyboard height the first time.
GalinhaVoadora
Updated on July 05, 2022Comments
-
GalinhaVoadora almost 2 years
I started creating a simple iOS app that does some operations.
But I'm having some problems when the keyboard appears, hiding one of my textfields.
I think it's a common problem and I did some research but I couldn't find anything that solved my problem.
I want to use a ScrollView rather than animate the textfield to make it visible.
-
Engnyl over 8 yearsUsing these code along with scrollView saved my life. Just add 20 contentInset.bottom to scroll it properly. contentInset.bottom = keyboardFrame.size.height + 20
-
Admin about 8 yearsWorks perfectly! The contentInset can be set to a 'let' instead of a 'var' in the keyboardWillHide though :)
-
makle over 7 yearsAlso maybe keep the textFieldShouldReturn method from above, if you want to use the return key for dismissing the keyboard. Thx for the update!
-
nikans about 7 yearsDon't forget to
NotificationCenter.default.removeObserver(self)
-
Siddharth about 7 yearsI get ViewController has no member for
self.scrollView
-
KMC about 7 years@Siddharth That's because you need to create IBOutlet for your scrollview
-
Rivers about 7 yearsWorks like a charm. Thanks Daniel.
-
PJayRushton about 7 years@nikans Apple Docs for
NotificationCenter
:If your app targets iOS 9.0 and later or macOS 10.11 and later, you don't need to unregister an observer in its deallocation method.
-
crashoverride777 almost 7 yearsNice answer. To be even more concise you can say ".UIKeyboardWillShow" instead of "NSNotification.Name.UIKeyboardWillShow". I updated your answer if you don't mind.
-
Gleb Tarasov over 6 yearsI think you need replace
UIKeyboardFrameBeginUserInfoKey
withUIKeyboardFrameEndUserInfoKey
in your answer. Because you need end frame in willShow method. -
iOS Geek almost 6 yearsAwesome , really Thanks
-
finngu almost 6 yearsThe first time the keyboard opens, its height is zero for me. Afterwards it works fine, though. Do you know of any bugs related to this?
-
Sam almost 6 yearsThis solution works when the UIControlView is scrolled.
-
Paul Cantrell over 5 years
UIKeyboardFrameBeginUserInfoKey
is the wrong key; it should beUIKeyboardFrameEndUserInfoKey
(begin → end). This solution gives zero height for the keyboard. -
Hashir Saeed over 5 yearsThis Helped me! Thanks Mate :)
-
Richard about 5 yearsGreat answer. I think it would be safer to use viewDidDisappear() - if the view is navigated away whilst the keyboard is visible it is conceivable you could remove the notification observer before the hide has actually taken place, and therefore not reset your content inset. To be extra sure I'd also set the inset to 0 whenever the notification observer is removed.
-
Leo almost 5 yearsPretty clean solution. In addition,
keyboardWillShow
andkeyboardWillHide
can be private. -
Elijah almost 5 yearsThanks @Richard. I have updated my answer accordingly.
-
jayant rawat over 4 yearsBinary operator '+' cannot be applied to operands of type 'UIEdgeInsets' and 'Double'
-
Sergey Didanov over 3 yearsFirst keyboardWillShow returns correct keyboard height (260). All next keyboardWillShow return keyboard height 216. Why?
-
Ben Shabat over 3 yearsDo we need to remove the observers when ViewController get destroyed?
-
Radu Ursache almost 3 years@BenShabat Apple Docs for
NotificationCenter
:If your app targets iOS 9.0 and later or macOS 10.11 and later, you don't need to unregister an observer in its deallocation method.
-
Roman over 2 yearsAwesome solution, man. Works like a charm. But you really, REALLY should have mentioned about NotificationCenter.default.addObserver first. Maybe that's why you still didn't get any likes!
-
Ben over 2 yearsFor Swift 5, NSNotification.Name.UIKeyboardWillShow is UIResponder.keyboardWillShowNotification and UIKeyboardFrameBeginUserInfoKey is UIResponder.keyboardFrameBeginUserInfoKey.