Skip to content

Instantly share code, notes, and snippets.

@danielpunkass
Created February 3, 2020 04:57
Show Gist options
  • Star 13 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save danielpunkass/4f26d9df5c38758e82c88944e14700ab to your computer and use it in GitHub Desktop.
Save danielpunkass/4f26d9df5c38758e82c88944e14700ab to your computer and use it in GitHub Desktop.
//
// UIView+RSKeyboardLayoutGuide.swift
// RSTouchUIKit
//
// Created by Daniel Jalkut on 12/23/18.
//
import UIKit
// Extends UIView to expose a keyboardLayoutGuide property that can be used to tie a view controller's content
// to so that it will automatically move out of the way when the keyboard appears.
extension UIView {
func layoutGuide(withIdentifier identifier: String) -> UILayoutGuide? {
// Should only ever be one with a matching identifier
return self.layoutGuides.filter { $0.identifier == identifier }.first
}
@objc(rsKeyboardLayoutGuide)
public var keyboardLayoutGuide: UILayoutGuide {
get {
let keyboardGuideIdentifier = "com.red-sweater.layoutguide.keyboard"
if let existingGuide = self.layoutGuide(withIdentifier: keyboardGuideIdentifier) {
return existingGuide
}
else {
let newGuide = UILayoutGuide()
newGuide.identifier = keyboardGuideIdentifier
self.addLayoutGuide(newGuide)
let heightConstraint = newGuide.heightAnchor.constraint(equalToConstant: 0)
heightConstraint.isActive = true
newGuide.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
// Subscribe to notifications of keyboard size change, so we can adapt our
// guide to match.
NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillChangeFrameNotification, object: nil, queue: nil) { [weak self] note in
guard
let existingSelf = self,
let noteInfo = note.userInfo,
let keyboardFrame = noteInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect,
let superview = existingSelf.superview
else {
return
}
let keyboardFrameInSuperview = superview.convert(keyboardFrame, from: nil)
let ourFrameInSuperview = superview.convert(existingSelf.frame, from: existingSelf)
// Special case - if the keyboard is floating we disregard its height. I don't know how
// to strictly tell if it's floating but when it is floating it seems to represent sometimes
// as having a 0 width or awkward 36 height. Let's just assume a keyboard width less than
// half the main screen width is floating.
let screenWidth = UIScreen.main.bounds.size.width
let isFloatingKeyboard = keyboardFrameInSuperview.width < (screenWidth / 2)
// The height anchor constant should be our view's max Y minus the keyboad frame's
// min Y, which is effectively the top edge of the keyboard.
let newKeyboardHeight = isFloatingKeyboard ? 0 : max(0.0, ourFrameInSuperview.maxY - keyboardFrameInSuperview.minY)
// Animate if we have pertinent animation info
if let animationDuration = noteInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? TimeInterval {
let animationCurve: UIView.AnimationCurve
if
let curveValue = noteInfo[UIResponder.keyboardAnimationCurveUserInfoKey] as? Int,
let validCurve = UIView.AnimationCurve(rawValue: curveValue)
{
animationCurve = validCurve
}
else {
animationCurve = .easeInOut
}
// Map from curve value to animation options
let animationOptions: UIView.AnimationOptions
switch animationCurve {
case .easeInOut:
animationOptions = .curveEaseInOut
case .easeIn:
animationOptions = .curveEaseIn
case .easeOut:
animationOptions = .curveEaseOut
case .linear:
animationOptions = .curveLinear
default:
animationOptions = .curveEaseInOut
}
UIView.animate(withDuration: animationDuration, delay: 0, options: animationOptions, animations: {
heightConstraint.constant = newKeyboardHeight
}, completion: nil)
}
else {
heightConstraint.constant = newKeyboardHeight
}
}
return newGuide
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment