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(_:)
and setting the textView's isScrollEnabled
property to false
, but it doesn't work.
I used to accomplish cell resizing by calling tableView.beginUpdates(); tableView.endUpdates()
in textViewDidChange(_:)
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.
I was also answered on the Apple developer forum: https://forums.developer.apple.com/forums/thread/749525?page=1#784404022.
The solution is to "Set the selfSizingInvalidation
property on the UICollectionView to .enabledIncludingConstraints
[and set the text view's isScrollEnabled
property to false]. This will cause the collection view to automatically detect Auto Layout and intrinsic content size changes within the cell, and resize the cell when necessary.
Links to the relevant documentation pages:
https://developer.apple.com/documentation/uikit/uicollectionview/4001100-selfsizinginvalidation
https://developer.apple.com/documentation/uikit/uicollectionview/selfsizinginvalidation/enabledincludingconstraints".
Note: this solution only works for iOS 16 or above but it is very convenient given that it should work without you having to mess with any delegate of any subview or to manually respond to events in general.