1

I wish to achieve keyboard and view resizing behaviour, as shown in iOS Messages. It looks something like

https://i.imgur.com/92rvHpo.mp4

I try with the following

Step 1

This ensures when our scroll down gesture hit the keyboard region, and keyboard will scroll down along with our gesture action.

collectionView.keyboardDismissMode = .interactive

Step 2

Adjust the bottom constraint, to ensure our collection view doesn't visually blocked by the keyboard. We adjust the bottom constraint when the keyboard finishes hiding, or keyboard finishes showing.

override func viewDidLoad() {
    super.viewDidLoad()
    
    initKeyboardNotifications()
}

//
// https://stackoverflow.com/a/41808338/72437
//
func initKeyboardNotifications() {
    // If your app targets iOS 9.0 and later or macOS 10.11 and later, you do not need to unregister an observer
    // that you created with this function.
    
    NotificationCenter.default.addObserver(self,
        selector: #selector(keyboardWillShow),
        name: UIResponder.keyboardWillShowNotification,
        object: nil)
    NotificationCenter.default.addObserver(self,
        selector: #selector(keyboardWillHide),
        name: UIResponder.keyboardWillHideNotification,
        object: nil)
}

@objc private func keyboardWillShow(sender: NSNotification) {
    let i = sender.userInfo!
    let s: TimeInterval = (i[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue
    let k = (i[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue.height
    bottomLayoutConstraint.constant = -k
    
    UIView.animate(withDuration: s) { self.view.layoutIfNeeded() }
}

@objc private func keyboardWillHide(sender: NSNotification) {
    let info = sender.userInfo!
    let s: TimeInterval = (info[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue
    bottomLayoutConstraint.constant = 0
    UIView.animate(withDuration: s) { self.view.layoutIfNeeded() }
}

This is our imperfect outcome so far.

enter image description here

I guess, I need to adjust the bottom constraint real-time, when keyboard is being dragged. But, I have no idea from where I can get the keyboard height information, when it is being dragged.

Do you have any idea, how I can adjust the bottom constraint real-time, when keyboard is being dragged?

Cheok Yan Cheng
  • 47,586
  • 132
  • 466
  • 875

2 Answers2

1

You can try adding observer for one more notification to observe keyboard height changes.

UIResponder.keyboardDidChangeFrameNotification

Then you will get current keyboard height and can adjust your layout accordingly.


UPDATE

Neither keyboardWillChangeFrameNotification nor keyboardDidChangeFrameNotification will receive notification during "drag down".

You could try adding an inputAccessoryView to your textField that can help you observe keyboard frame changes.

import UIKit

class FrameObservingInputAccessoryView: UIView {
    var onFrameChange: ((_ frame: CGRect) -> Void)?
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        self.commonSetUp()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        self.commonSetUp()
    }
    
    private var frameObservation: NSKeyValueObservation?
    override func willMove(toSuperview newSuperview: UIView?) {
        frameObservation?.invalidate()
        frameObservation = newSuperview?.observe(\.frame, changeHandler: { [weak self] (_, _) in
            guard let self = self, let superview = self.superview else { return }
            self.onFrameChange?(superview.frame)
        })
        super.willMove(toSuperview: newSuperview)
    }
    
    deinit {
        frameObservation?.invalidate()
    }
    
    private func commonSetUp() {
        self.isUserInteractionEnabled = false
    }
}

Usage

class MyCollectionViewCell: UICollectionViewCell, UITextFieldDelegate {
    var onShouldBeginEditing: ((_ textField: UITextField) -> Void)?
    
    func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
        onShouldBeginEditing?(textField)
        return true
    }
}

class ViewController: UIViewController, UICollectionViewDataSource {
    lazy var frameObservingView: FrameObservingInputAccessoryView = {
        let frameObservingView = FrameObservingInputAccessoryView(frame: .zero)
        frameObservingView.onFrameChange = { [weak self] (frame) in
            // update your constraint
        }
        return frameObservingView
    }()
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        5
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MyCollectionViewCell", for: indexPath) as! MyCollectionViewCell
        cell.onShouldBeginEditing = { [weak self] (textField) in
            textField.inputAccessoryView = self?.frameObservingView
        }
        return cell
    }
}
Tarun Tyagi
  • 9,364
  • 2
  • 17
  • 30
0

Rather than changing the bottomConstraint you should instead adjust the contentInset.bottom of your collectionView when the keyboard shows/hides:

@objc private func keyboardWillShow(sender: NSNotification) {
    let i = sender.userInfo!
    let s: TimeInterval = (i[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue
    let k = (i[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue.height
    collectionView.contentInset.bottom = k
    UIView.animate(withDuration: s) { self.view.layoutIfNeeded() }
}

@objc private func keyboardWillHide(sender: NSNotification) {
    let info = sender.userInfo!
    let s: TimeInterval = (info[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue
    collectionView.contentInset.bottom = 0
    UIView.animate(withDuration: s) { self.view.layoutIfNeeded() }
}
samaitch
  • 1,172
  • 7
  • 9