skip to Main Content

This is a simple collection view with compositional layout and diffable data source.

It displays one cell, of type UICollectionViewListCell, whose contentView has a text view as a subview.

import UIKit

class ViewController: UIViewController {
    var collectionView: UICollectionView!
    let textView = UITextView()
    
    var dataSource: UICollectionViewDiffableDataSource<Section, Int>!
    
    enum Section: CaseIterable {
        case first
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        configureHierarchy()
        configureDataSource()
    }
    
    private func configureHierarchy() {
        collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: createLayout())
        view.addSubview(collectionView)
        collectionView.autoresizingMask = [.flexibleHeight, .flexibleWidth]

        textView.delegate = self
    }
    
    func configureDataSource() {
        let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, Int> { [weak self] cell, indexPath, itemIdentifier in
            guard let self else { return }
            
            cell.contentView.addSubview(textView)
            textView.pinToSuperviewMargins()
        }
        
        dataSource = .init(collectionView: collectionView) { collectionView, indexPath, itemIdentifier in
            collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: itemIdentifier)
        }
        
        var snapshot = NSDiffableDataSourceSnapshot<Section, Int>()
        snapshot.appendSections(Section.allCases)
        snapshot.appendItems([1], toSection: .first)
        dataSource.apply(snapshot)
    }
    
    func createLayout() -> UICollectionViewLayout {
        UICollectionViewCompositionalLayout { section, layoutEnvironment in
            var config = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
            return NSCollectionLayoutSection.list(using: config, layoutEnvironment: layoutEnvironment)
        }
    }
}
extension ViewController: UITextViewDelegate {
    func textViewDidChange(_ textView: UITextView) {
        // Do something here?
    }
}

The pinToSuperviewMargins method sets the top, bottom, leading and trailing constraints of the view on which it’s called to its superview’s and its translatesAutoResizingMaskIntoConstraints property to false:

extension UIView {
    func pinToSuperviewMargins(
        top: CGFloat = 0,
        bottom: CGFloat = 0,
        leading: CGFloat = 0,
        trailing: CGFloat = 0,
        file: StaticString = #file,
        line: UInt = #line
    ) {
        guard let superview = self.superview else {
            let localFilePath = URL(fileURLWithPath: "(file)").lastPathComponent
            print(">> (#function) failed in file: (localFilePath), at line: (line): could not find (Self.self).superView.")
            return
        }
        
        self.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            self.topAnchor.constraint(equalTo: superview.topAnchor, constant: top),
            self.bottomAnchor.constraint(equalTo: superview.bottomAnchor, constant: bottom),
            self.leadingAnchor.constraint(equalTo: superview.leadingAnchor, constant: leading),
            self.trailingAnchor.constraint(equalTo: superview.trailingAnchor, constant: trailing),
        ])
    }
    
    func pinToSuperviewMargins(constant c: CGFloat = 0, file: StaticString = #file, line: UInt = #line) {
        self.pinToSuperviewMargins(top: c, bottom: c, leading: c, trailing: c, file: file, line: line)
    }
}

I tried calling collectionView.setNeedsLayout() in textViewDidChange(_:) but it doesn’t work.

I used to accomplish cell resizing with tableView.beginUpdates(); tableView.endUpdates() when dealing with table views.

I’ve read in a different post someone asking "Why are you putting a text view inside a collection view cell?", and similar questions have popped out under many of my questions, so let me kindly ask you in advance to not ask anything unrelated to the question until it’s been answered.

3

Answers


  1. Chosen as BEST ANSWER

    I was answered on the Apple developer forum: https://forums.developer.apple.com/forums/thread/749525?page=1#784404022.

    The solution only works for iOS 16 or above but it animates the resizing.


  2. In order to get a UITextView to "auto-size" its height, we have to disable scrolling:

        textView.isScrollEnabled = false
        cell.contentView.addSubview(textView)
    

    Then, in your textViewDidChange() handler, tell the collection view it needs to re-layout its cells:

    func textViewDidChange(_ textView: UITextView) {
        // Do something here?
        collectionView.collectionViewLayout.invalidateLayout()
    }
    
    Login or Signup to reply.
  3. Solution for iOS 15 that also animates the resizing: disable text view scrolling (textView.isScrollEnabled = false), and then in the textViewDidChange(_:) method of UITextViewDelegate reconfigure the items of the data source:

    extension ViewController: UITextViewDelegate {
        func textViewDidChange(_ textView: UITextView) {
            // Do something here?
            var snapshot = dataSource.snapshot()
            snapshot.reconfigureItems([1])
            dataSource.apply(snapshot)
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search