Handle keyboard layout in iOS 15+ UIKit app with collection view using the modern approach

The following is a UIKit app that uses a collection view with list layout and a diffable data source.

It displays one section that has 10 empty cells and then final cell whose content view contains a text view, that is pinned to the content view's layout margins guide.

The text view's scrolling is set to false, so that the line collectionView.selfSizingInvalidation = .enabledIncludingConstraints will succeed at making the text view's cell resize automatically and animatedly as the text changes.

import UIKit

class ViewController: UIViewController {
    var collectionView: UICollectionView!
    
    var dataSource: UICollectionViewDiffableDataSource<String, Int>!
    
    let textView: UITextView = {
      let tv = UITextView()
        tv.text = "Text"
        tv.isScrollEnabled = false
        return tv
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        configureHierarchy()
        configureDataSource()
        
        if #available(iOS 16.0, *) {
            collectionView.selfSizingInvalidation = .enabledIncludingConstraints
        }
    }

    func configureHierarchy() {
        collectionView = .init(frame: .zero, collectionViewLayout: createLayout())
        view.addSubview(collectionView)
        collectionView.frame = view.bounds
        collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    }
    
    func createLayout() -> UICollectionViewLayout {
        let configuration = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
        return UICollectionViewCompositionalLayout.list(using: configuration)
    }
    
    func configureDataSource() {
        let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, Int> { _, _, _ in }
        let textViewCellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, Int> { [weak self] cell, _, _ in
            guard let self else { return }
            
            cell.contentView.addSubview(textView)
            textView.pin(to: cell.contentView.layoutMarginsGuide)
        }
        
        dataSource = .init(collectionView: collectionView) { collectionView, indexPath, itemIdentifier in
            if indexPath.row == 10 {
                collectionView.dequeueConfiguredReusableCell(using: textViewCellRegistration, for: indexPath, item: itemIdentifier)
            } else {
                collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: itemIdentifier)
            }
        }
        
        var snapshot = NSDiffableDataSourceSnapshot<String, Int>()
        snapshot.appendSections(["section"])
        snapshot.appendItems(Array(0...10))
        dataSource.apply(snapshot)
    }
}

extension UIView {
    func pin(
        to object: CanBePinnedTo,
        top: CGFloat = 0,
        bottom: CGFloat = 0,
        leading: CGFloat = 0,
        trailing: CGFloat = 0
    ) {
        self.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            self.topAnchor.constraint(equalTo: object.topAnchor, constant: top),
            self.bottomAnchor.constraint(equalTo: object.bottomAnchor, constant: bottom),
            self.leadingAnchor.constraint(equalTo: object.leadingAnchor, constant: leading),
            self.trailingAnchor.constraint(equalTo: object.trailingAnchor, constant: trailing),
        ])
    }
}

@MainActor
protocol CanBePinnedTo {
    var topAnchor: NSLayoutYAxisAnchor { get }
    var bottomAnchor: NSLayoutYAxisAnchor { get }
    var leadingAnchor: NSLayoutXAxisAnchor { get }
    var trailingAnchor: NSLayoutXAxisAnchor { get }
}

extension UIView: CanBePinnedTo { }
extension UILayoutGuide: CanBePinnedTo { }

How do I make the UI move to accomodate the keyboard once you tap on the text view and also when the text view changes size, by activating the view.keyboardLayoutGuide.topAnchor constraint, as shown in the WWDC21 video "Your guide to keyboard layout"?

My code does not resize the text view on iOS 15, only on iOS 16+, so clearly the solution may allow the UI to adjust to text view changes on iOS 16+ only.

Recommended, modern, approach:

Not recommended, old, approach:

I've tried to say view.keyboardLayoutGuide.topAnchor.constraint(equalTo: textView.bottomAnchor).isActive = true in the text view cell registration, as well as view.keyboardLayoutGuide.topAnchor.constraint(equalTo: collectionView.bottomAnchor).isActive = true in viewDidLoad(), but both of these approaches fail (Xcode 15.3 iPhone 15 Pro simulator with iOS 17.4, physical iPhone SE on iOS 15.8).

Replies

I can't edit the post here anymore so go check out the stackoverflow.com post which is up to date: https://stackoverflow.com/questions/78373006/handle-keyboard-layout-in-ios-15-uikit-app-with-collection-view-using-the-moder