Skip to content

Instantly share code, notes, and snippets.

@danielpunkass
Created February 3, 2020 04:57
Show Gist options
  • Select an option

  • Save danielpunkass/4f26d9df5c38758e82c88944e14700ab to your computer and use it in GitHub Desktop.

Select an option

Save danielpunkass/4f26d9df5c38758e82c88944e14700ab to your computer and use it in GitHub Desktop.

Revisions

  1. danielpunkass created this gist Feb 3, 2020.
    104 changes: 104 additions & 0 deletions UIView+RSKeyboardLayoutGuide.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,104 @@
    //
    // 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
    }
    }
    }
    }