Last active
October 14, 2021 10:10
-
-
Save grigorevp/c83f3154959c8efa8d3b547315f732fe to your computer and use it in GitHub Desktop.
Automatic keyboard appearance handling for UIKit
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
// UIScreenAdjuster.swift | |
// Created by Petr Grigorev | |
// This code could be used for automatic keyboard appearance handling. | |
// It automatically moves screen whenever a new text editing session from | |
// UITextField or UITextView is recognized. | |
// | |
// To start using it, just declare a constant inside your AppDelegate, like: | |
// | |
// private let screenAdjuster = UIScreenAdjuster() | |
import UserNotifications | |
import UIKit | |
class UIScreenAdjuster { | |
private let animationDuration: TimeInterval = 0.4 | |
private let safeZoneRatio: CGFloat = 0.2 | |
private var isKeyboardVisible = false | |
private var firstResponder: UIView? | |
private var topViewController: UIViewController? { | |
get { | |
let keyWindow = UIApplication.shared.windows.filter {$0.isKeyWindow}.first | |
if var topController = keyWindow?.rootViewController { | |
while let presentedViewController = topController.presentedViewController { | |
topController = presentedViewController | |
} | |
return topController | |
} | |
return nil | |
} | |
} | |
private var currentOffset: CGFloat = 0.0 | |
@objc private func keyboardWillShow(_ notification: Notification) { | |
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01, execute: { [weak self] in | |
if let self = self { | |
if let vc = self.topViewController, let firstResponder = self.firstResponder, let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue { | |
if let frame = firstResponder.superview?.convert(firstResponder.frame, to: vc.view) { | |
let availableScreenHeight = vc.view.frame.height - keyboardSize.height | |
let minSafeY = availableScreenHeight * self.safeZoneRatio | |
let maxSafeY = availableScreenHeight - availableScreenHeight * self.safeZoneRatio | |
let tempCurrentOffset = self.currentOffset | |
let maxYDifference = frame.maxY - maxSafeY | |
if maxYDifference > 0 { | |
self.currentOffset -= maxYDifference | |
} | |
let minYDifference = minSafeY - frame.minY | |
if minYDifference > 0 { | |
self.currentOffset += minYDifference | |
} | |
self.currentOffset > 0 ? self.currentOffset = 0 : nil | |
self.currentOffset < -keyboardSize.height ? self.currentOffset = -keyboardSize.height : nil | |
UIView.animate(withDuration: self.animationDuration, animations: { | |
for view in vc.view.subviews { | |
view.frame.origin = CGPoint(x: view.frame.origin.x, y: view.frame.origin.y - tempCurrentOffset + self.currentOffset) | |
} | |
}) | |
self.isKeyboardVisible = true | |
} | |
} | |
} | |
}) | |
} | |
@objc private func keyboardWillHide(_ notification: Notification) { | |
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01, execute: { [weak self] in | |
if let self = self { | |
if let vc = self.topViewController { | |
UIView.animate(withDuration: self.animationDuration, animations: { | |
for view in vc.view.subviews { | |
view.frame.origin = CGPoint(x: view.frame.origin.x, y: view.frame.origin.y - self.currentOffset) | |
} | |
// vc.view.frame.origin = CGPoint(x: 0, y: self.currentOffset) | |
}) | |
self.currentOffset = 0 | |
// UIView.animate(withDuration: duration, animations: { | |
// vc.view.frame.origin = .zero | |
// }) | |
// | |
self.isKeyboardVisible = false | |
} | |
} | |
}) | |
} | |
@objc private func textDidBeginEditing(_ notification: Notification) { | |
if let view = notification.object as? UIView { | |
firstResponder = view | |
} | |
} | |
init() { | |
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil) | |
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil) | |
NotificationCenter.default.addObserver(self, selector: #selector(textDidBeginEditing(_:)), name: UITextField.textDidBeginEditingNotification, object: nil) | |
NotificationCenter.default.addObserver(self, selector: #selector(textDidBeginEditing(_:)), name: UITextView.textDidBeginEditingNotification, object: nil) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment