Last active
September 7, 2020 21:46
-
-
Save zackdotcomputer/66cc54a50c3cd763022067de5e088466 to your computer and use it in GitHub Desktop.
Make UIViewControllers automatically aware of the keyboard
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// UIViewController+KeyboardAware.swift | |
// | |
// Created by Zack Sheppard. | |
// Copyright © 2020 Zack Sheppard. All rights reserved. | |
// Available under the MIT License | |
// | |
import UIKit | |
/// Conforming to this protocol and calling startAvoidingKeyboard will cause | |
/// your view to automatically shrink to avoid the keyboard when it comes | |
/// on screen. | |
@objc protocol KeyboardAware { | |
/// Implement this function for additional animation responses to the keyboard | |
func customAnimationForNewKeyboardFrame(_ frame: CGRect) | |
} | |
// System static variable for the keyboard - is true if it is currently moving | |
fileprivate var isKeyboardMoving: Bool = false | |
/// This extension gives UIViewControllers automatic KeyboardAware ability, if you call startAvoidingKeyboard. | |
/// Based on idea from https://stackoverflow.com/questions/45399178/ | |
extension UIViewController: KeyboardAware { | |
var keyboardIsAnimating: Bool { | |
return isKeyboardMoving | |
} | |
/// This view controller should start resizing itself to avoid the keyboard | |
func startAvoidingKeyboard() { | |
NotificationCenter.default.addObserver( | |
self, | |
selector: #selector(handleKeyboardFrameWillChangeNotification(_:)), | |
name: UIResponder.keyboardWillChangeFrameNotification, | |
object: nil | |
) | |
} | |
/// This view controller should stop resizing itself to avoid the keyboard | |
func stopAvoidingKeyboard() { | |
NotificationCenter.default.removeObserver( | |
self, | |
name: UIResponder.keyboardWillChangeFrameNotification, | |
object: nil | |
) | |
self.additionalSafeAreaInsets.bottom = 0 | |
} | |
func customAnimationForNewKeyboardFrame(_ frame: CGRect) { | |
// Feel free to override this to add custom handling | |
} | |
@objc private func handleKeyboardFrameWillChangeNotification(_ notification: Notification) { | |
guard let userInfo = notification.userInfo, | |
let keyboardFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue else { | |
isKeyboardMoving = false | |
return | |
} | |
let keyboardFrameInView = view.convert(keyboardFrame, from: nil) | |
let safeAreaFrame = view.safeAreaLayoutGuide.layoutFrame.insetBy(dx: 0, dy: -additionalSafeAreaInsets.bottom) | |
let intersection = safeAreaFrame.intersection(keyboardFrameInView) | |
let animationDuration: TimeInterval = (notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0 | |
let animationCurve: UIView.AnimationOptions | |
if let animationCurveRawValue = (notification.userInfo?[UIResponder.keyboardAnimationCurveUserInfoKey] as? NSNumber)?.uintValue { | |
animationCurve = UIView.AnimationOptions(rawValue: animationCurveRawValue) | |
} else { | |
animationCurve = UIView.AnimationOptions.curveEaseInOut | |
} | |
isKeyboardMoving = true | |
UIView.animate(withDuration: animationDuration, delay: 0, options: animationCurve, animations: { | |
self.additionalSafeAreaInsets.bottom = intersection.height | |
self.customAnimationForNewKeyboardFrame(intersection) | |
self.view.layoutIfNeeded() | |
}, completion: { (_) in | |
isKeyboardMoving = false | |
}) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment