07-21-2023, 10:31 PM
One of the easiest solutions is using the following protocol:
protocol ScrollViewKeyboardDelegate: class {
var scrollView: UIScrollView? { get set }
func registerKeyboardNotifications()
func unregisterKeyboardNotifications()
}
extension ScrollViewKeyboardDelegate where Self: UIViewController {
func registerKeyboardNotifications() {
NotificationCenter.default.addObserver(
forName: UIResponder.keyboardWillChangeFrameNotification,
object: nil,
queue: nil) { [weak self] notification in
self?.keyboardWillBeShown(notification)
}
NotificationCenter.default.addObserver(
forName: UIResponder.keyboardWillHideNotification,
object: nil,
queue: nil) { [weak self] notification in
self?.keyboardWillBeHidden(notification)
}
}
func unregisterKeyboardNotifications() {
NotificationCenter.default.removeObserver(
self,
name: UIResponder.keyboardWillChangeFrameNotification,
object: nil
)
NotificationCenter.default.removeObserver(
self,
name: UIResponder.keyboardWillHideNotification,
object: nil
)
}
func keyboardWillBeShown(_ notification: Notification) {
let info = notification.userInfo
let key = (info?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)
let aKeyboardSize = key?.cgRectValue
guard let keyboardSize = aKeyboardSize,
let scrollView = self.scrollView else {
return
}
let bottomInset = keyboardSize.height
scrollView.contentInset.bottom = bottomInset
scrollView.scrollIndicatorInsets.bottom = bottomInset
if let activeField = self.view.firstResponder {
let yPosition = activeField.frame.origin.y - bottomInset
if yPosition > 0 {
let scrollPoint = CGPoint(x: 0, y: yPosition)
scrollView.setContentOffset(scrollPoint, animated: true)
}
}
}
func keyboardWillBeHidden(_ notification: Notification) {
self.scrollView?.contentInset = .zero
self.scrollView?.scrollIndicatorInsets = .zero
}
}
extension UIView {
var firstResponder: UIView? {
guard !isFirstResponder else { return self }
return subviews.first(where: {$0.firstResponder != nil })
}
}
When you want to use this protocol, you only need to conform to it and assign your scroll view in your controller as follows:
class MyViewController: UIViewController {
@IBOutlet var scrollViewOutlet: UIScrollView?
var scrollView: UIScrollView?
public override func viewDidLoad() {
super.viewDidLoad()
self.scrollView = self.scrollViewOutlet
self.scrollView?.isScrollEnabled = true
self.registerKeyboardNotifications()
}
extension MyViewController: ScrollViewKeyboardDelegate {}
deinit {
self.unregisterKeyboardNotifications()
}
}
protocol ScrollViewKeyboardDelegate: class {
var scrollView: UIScrollView? { get set }
func registerKeyboardNotifications()
func unregisterKeyboardNotifications()
}
extension ScrollViewKeyboardDelegate where Self: UIViewController {
func registerKeyboardNotifications() {
NotificationCenter.default.addObserver(
forName: UIResponder.keyboardWillChangeFrameNotification,
object: nil,
queue: nil) { [weak self] notification in
self?.keyboardWillBeShown(notification)
}
NotificationCenter.default.addObserver(
forName: UIResponder.keyboardWillHideNotification,
object: nil,
queue: nil) { [weak self] notification in
self?.keyboardWillBeHidden(notification)
}
}
func unregisterKeyboardNotifications() {
NotificationCenter.default.removeObserver(
self,
name: UIResponder.keyboardWillChangeFrameNotification,
object: nil
)
NotificationCenter.default.removeObserver(
self,
name: UIResponder.keyboardWillHideNotification,
object: nil
)
}
func keyboardWillBeShown(_ notification: Notification) {
let info = notification.userInfo
let key = (info?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)
let aKeyboardSize = key?.cgRectValue
guard let keyboardSize = aKeyboardSize,
let scrollView = self.scrollView else {
return
}
let bottomInset = keyboardSize.height
scrollView.contentInset.bottom = bottomInset
scrollView.scrollIndicatorInsets.bottom = bottomInset
if let activeField = self.view.firstResponder {
let yPosition = activeField.frame.origin.y - bottomInset
if yPosition > 0 {
let scrollPoint = CGPoint(x: 0, y: yPosition)
scrollView.setContentOffset(scrollPoint, animated: true)
}
}
}
func keyboardWillBeHidden(_ notification: Notification) {
self.scrollView?.contentInset = .zero
self.scrollView?.scrollIndicatorInsets = .zero
}
}
extension UIView {
var firstResponder: UIView? {
guard !isFirstResponder else { return self }
return subviews.first(where: {$0.firstResponder != nil })
}
}
When you want to use this protocol, you only need to conform to it and assign your scroll view in your controller as follows:
class MyViewController: UIViewController {
@IBOutlet var scrollViewOutlet: UIScrollView?
var scrollView: UIScrollView?
public override func viewDidLoad() {
super.viewDidLoad()
self.scrollView = self.scrollViewOutlet
self.scrollView?.isScrollEnabled = true
self.registerKeyboardNotifications()
}
extension MyViewController: ScrollViewKeyboardDelegate {}
deinit {
self.unregisterKeyboardNotifications()
}
}