It’s best to listen to keyboardWillChangeFrameNotification as it contains frame changes for Keyboard in different situation like custom keyboard, languages.
Posted immediately prior to a change in the keyboard’s frame.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
classKeyboardHandler{ let scrollView: UIScrollView let stackView: UIStackView var observer: AnyObject? var keyboardHeightConstraint: NSLayoutConstraint!
structInfo{ let frame: CGRect let duration: Double let animationOptions: UIView.AnimationOptions }
To make scrollView scroll beyond its contentSize, we can change its contentInset.bottom. Another way is to add a dummy view with certain height to UIStackView and alter its NSLayoutConstraintconstant
We can’t access self inside init, so it’s best to have setup function
1 2 3 4 5 6 7 8 9 10 11 12 13 14
funcsetup() { let space = UIView() keyboardHeightConstraint = space.heightAnchor.constraint(equalToConstant: 0) NSLayoutConstraint.on([keyboardHeightConstraint]) stackView.addArrangedSubview(spa observer = NotificationCenter.default.addObserver( forName: UIResponder.keyboardWillChangeFrameNotification, object: nil, queue: .main, using: { [weakself] notification in self?.handle(notification) } ) }
Convert Notification to a convenient Info struct
1 2 3 4 5 6 7 8 9 10 11 12 13 14
funcconvert(notification: Notification) -> Info? { guard let frameValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] NSValue, let durationotification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber, let raw = notification.userInfo?[UIResponder.keyboardAnimationCurveUserInfoKey] NSNumber else { returnnil returnInfo( frame: frameValue.cgRectValue, duration: duration.doubleValue, animationOptions: UIView.AnimationOptions(rawValue: raw.uintValue) ) }
Then we can compare with UIScreen to check if Keyboard is showing or hiding
This method scrolls the content view so that the area defined by rect is just visible inside the scroll view. If the area is already visible, the method does nothing.
Another way is to check if keyboard overlaps UITextField. To do that we use convertRect:toView: with nil target so it uses window coordinates. Since keyboard frame is always relative to window, we have frames in same coordinate space.
Converts a rectangle from the receiver’s coordinate system to that of another view.
rect: A rectangle specified in the local coordinate system (bounds) of the receiver. view: The view that is the target of the conversion operation. If view is nil, this method instead converts to window base coordinates. Otherwise, both view and the receiver must belong to the same UIWindow object.
funcmove(info: Info) { let isHiding = info.frame.origin.y == UIScreen.main.bounds.height let moveUp = CGAffineTransform(translationX: 0, y: -info.frame.height)
switch (view.transform, isHiding) { case (.identity, false): view.transform = moveUp case (moveUp, true): view.transform = .identity default: break } }
Prefer willShow and willHide
There ‘s an edge case with the above switch on view.transform and isHiding with one time verification sms code, which make it into the correct case handling. It’s safe to just set view.transform depending on show with willHide and willShow
classKeyboardHandler{ let view: UIView var observerForWillShow: AnyObject? var observerForWillHide: AnyObject? var keyboardHeightConstraint: NSLayoutConstraint!
structInfo{ let frame: CGRect let duration: Double let animationOptions: UIView.AnimationOptions }