Skip to content

Instantly share code, notes, and snippets.

@Sorix
Last active May 21, 2022 16:03
Show Gist options
  • Save Sorix/2c08b36f020658d153c57bbed6f028a8 to your computer and use it in GitHub Desktop.
Save Sorix/2c08b36f020658d153c57bbed6f028a8 to your computer and use it in GitHub Desktop.
Report keyboard height changes

Keyboard Frame Changes Observer

Set of protocols that simplifies work with adding extra space when keyboard is shown, hidden or changed.

Features:

  • Correctly works with keyboard frame changes (e.g. keyboard height changes like emojii → normal keyboard).
  • TabBar & ToolBar support for UITableView example (at other examples you receive incorrect insets).
  • Dynamic animation duration (not hard-coded).
  • Protocol-oriented approach that could be easily modified for you purposes.

Usage

Basic usage example in view controller that contains some scroll view.

class SomeViewController: UIViewController {
  @IBOutlet weak var scrollView: UIScrollView!

  override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    addKeyboardFrameChangesObserver()
  }

  override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    removeKeyboardFrameChangesObserver()
  }
}

extension SomeViewController: ModifableInsetsOnKeyboardFrameChanges {
  var scrollViewToModify: UIScrollView { return scrollView }
}

Core: frame changes observer

Protocol KeyboardChangeFrameObserver will fire event each time keyboard frame was changed (including showing, hiding, frame changing).

  1. Call addKeyboardFrameChangesObserver() at viewWillAppear() or similar method.
  2. Call removeKeyboardFrameChangesObserver() at viewWillDisappear() or similar method.

Implementation: scrolling view

ModifableInsetsOnKeyboardFrameChanges protocol adds UIScrollView support to core protocol. It changes scroll view's insets when keyboard frame is changed.

Your class needs to set scroll view, one's insets will be increased / decreased on keyboard frame changes.

var scrollViewToModify: UIScrollView { get }
import UIKit
/// Observer that will fire events when keyboard frame will be changed (shown, hidden or resized)
/// - Note: Call `addKeyboardFrameChangesObserver()` on init, e.g. on `viewWillAppear`
/// and `removeKeyboardFrameChangesObserver()` on deinit, e.g. on `viewDidDisappear`
public protocol KeyboardChangeFrameObserver: class {
func willChangeKeyboardFrame(height: CGFloat, animationDuration: TimeInterval, animationOptions: UIView.AnimationOptions)
}
public extension KeyboardChangeFrameObserver {
func addKeyboardFrameChangesObserver() {
let center = NotificationCenter.default
center.addObserver(forName: UIResponder.keyboardWillChangeFrameNotification, object: nil, queue: .main) { [weak self] (notification) in
self?.sendDelegate(notification: notification, willHide: false)
}
center.addObserver(forName: UIResponder.keyboardWillHideNotification, object: nil, queue: .main) { [weak self] (notification) in
self?.sendDelegate(notification: notification, willHide: true)
}
}
func removeKeyboardFrameChangesObserver() {
NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil)
}
private func sendDelegate(notification: Notification, willHide: Bool) {
guard let userInfo = notification.userInfo,
let animationDuration = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double,
let keyboardEndFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue,
let rawAnimationCurveNumber = userInfo[UIResponder.keyboardAnimationCurveUserInfoKey] as? NSNumber else { return }
let rawAnimationCurve = rawAnimationCurveNumber.uint32Value << 16
let animationCurve = UIView.AnimationOptions(rawValue: UInt(rawAnimationCurve))
let keyboardHeight = willHide ? 0 : keyboardEndFrame.height
willChangeKeyboardFrame(height: keyboardHeight,
animationDuration: animationDuration,
animationOptions: [.beginFromCurrentState, animationCurve])
}
}
import UIKit
/// Insets of a scroll view will be changed when keyboard will appear
protocol ModifableInsetsOnKeyboardFrameChanges: KeyboardChangeFrameObserver {
/// Insets of that scroll view will be modified on keyboard appearance
var scrollViewToModify: UIScrollView { get }
}
/// Default implementation for `UIViewController`
extension ModifableInsetsOnKeyboardFrameChanges where Self: UIViewController {
func willChangeKeyboardFrame(height: CGFloat, animationDuration: TimeInterval, animationOptions: UIView.AnimationOptions) {
var adjustedHeight = height
if let tabBarHeight = self.tabBarController?.tabBar.frame.height {
adjustedHeight -= tabBarHeight
} else if let toolbarHeight = navigationController?.toolbar.frame.height, navigationController?.isToolbarHidden == false {
adjustedHeight -= toolbarHeight
}
if adjustedHeight < 0 { adjustedHeight = 0 }
UIView.animate(withDuration: animationDuration, animations: {
let newInsets = UIEdgeInsets(top: 0, left: 0, bottom: adjustedHeight, right: 0)
self.scrollViewToModify.contentInset = newInsets
self.scrollViewToModify.scrollIndicatorInsets = newInsets
})
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment