Please run the following UIKit app.
It uses a collection view with compositional layout (list layout) and a diffable data source.
The collection view has one section with one row.
The cell contains a text field that is pinned to its contentView
.
import UIKit
class ViewController: UIViewController {
var collectionView: UICollectionView!
var dataSource: UICollectionViewDiffableDataSource<String, String>!
override func viewDidLoad() {
super.viewDidLoad()
configureHierarchy()
configureDataSource()
}
func configureHierarchy() {
collectionView = .init(frame: .zero, collectionViewLayout: createLayout())
view.addSubview(collectionView)
collectionView.frame = view.bounds
}
func createLayout() -> UICollectionViewLayout {
UICollectionViewCompositionalLayout { section, layoutEnvironment in
let config = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
return NSCollectionLayoutSection.list(using: config, layoutEnvironment: layoutEnvironment)
}
}
func configureDataSource() {
let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, String> { cell, indexPath, itemIdentifier in
let textField = UITextField()
textField.placeholder = "Placeholder"
textField.font = .systemFont(ofSize: 100)
cell.contentView.addSubview(textField)
textField.pinToSuperview()
}
dataSource = .init(collectionView: collectionView) { collectionView, indexPath, itemIdentifier in
collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: itemIdentifier)
}
var snapshot = NSDiffableDataSourceSnapshot<String, String>()
snapshot.appendSections(["main"])
snapshot.appendItems(["demo"])
dataSource.apply(snapshot, animatingDifferences: false)
}
}
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),
])
}
func pinToSuperview(
top: CGFloat = 0,
bottom: CGFloat = 0,
leading: CGFloat = 0,
trailing: CGFloat = 0,
file: StaticString = #file,
line: UInt = #line
) {
guard let superview = self.superview else {
print(">> \(#function) failed in file: \(String.localFilePath(from: file)), at line: \(line): could not find \(Self.self).superView.")
return
}
self.pin(to: superview, top: top, bottom: bottom, leading: leading, trailing: trailing)
}
func pinToSuperview(constant c: CGFloat = 0, file: StaticString = #file, line: UInt = #line) {
self.pinToSuperview(top: c, bottom: -c, leading: c, trailing: -c, file: file, line: line)
}
}
@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 { }
extension String {
static func localFilePath(from fullFilePath: StaticString = #file) -> Self {
URL(fileURLWithPath: "\(fullFilePath)").lastPathComponent
}
}
Unfortunately, as soon as I insert a leading view in the cell:
let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, String> { cell, indexPath, itemIdentifier in
let contentView = cell.contentView
let leadingView = UIView()
leadingView.backgroundColor = .systemRed
let textField = UITextField()
textField.placeholder = "Placeholder"
textField.font = .systemFont(ofSize: 100)
contentView.addSubview(leadingView)
contentView.addSubview(textField)
leadingView.translatesAutoresizingMaskIntoConstraints = false
textField.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
leadingView.centerYAnchor.constraint(equalTo: contentView.layoutMarginsGuide.centerYAnchor),
leadingView.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
leadingView.widthAnchor.constraint(equalTo: contentView.layoutMarginsGuide.heightAnchor),
leadingView.heightAnchor.constraint(equalTo: contentView.layoutMarginsGuide.heightAnchor),
textField.topAnchor.constraint(equalTo: contentView.topAnchor),
textField.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
textField.leadingAnchor.constraint(equalTo: leadingView.trailingAnchor, constant: 16),
textField.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor),
textField.heightAnchor.constraint(greaterThanOrEqualToConstant: 44)
])
}
the cell does not self-size, and in particular it does not accomodate the text field:
What would be the best way to make the cell resize automatically?
Use UIListContentConfiguration:
import UIKit
class ViewController: UIViewController {
var collectionView: UICollectionView!
var dataSource: UICollectionViewDiffableDataSource<String, String>!
override func viewDidLoad() {
super.viewDidLoad()
configureHierarchy()
configureDataSource()
}
func configureHierarchy() {
collectionView = .init(frame: .zero, collectionViewLayout: createLayout())
view.addSubview(collectionView)
collectionView.frame = view.bounds
}
func createLayout() -> UICollectionViewLayout {
UICollectionViewCompositionalLayout { section, layoutEnvironment in
let config = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
return NSCollectionLayoutSection.list(using: config, layoutEnvironment: layoutEnvironment)
}
}
func configureDataSource() {
let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, String> { cell, indexPath, itemIdentifier in
var contentConfig = CustomListContentConfiguration()
contentConfig.placeholder = "Placeholder"
cell.contentConfiguration = contentConfig
}
dataSource = .init(collectionView: collectionView) { collectionView, indexPath, itemIdentifier in
collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: itemIdentifier)
}
var snapshot = NSDiffableDataSourceSnapshot<String, String>()
snapshot.appendSections(["main"])
snapshot.appendItems(["demo"])
dataSource.apply(snapshot, animatingDifferences: false)
}
}
class CustomListContentConfiguration: UIContentConfiguration {
var placeholder: String?
func makeContentView() -> UIView & UIContentView {
return CustomListContentView(configuration: self)
}
func updated(for state: UIConfigurationState) -> Self {
// Not handling state changes in this example, so just return self
return self
}
}
class CustomListContentView: UIView, UIContentView {
var configuration: UIContentConfiguration
init(configuration: UIContentConfiguration) {
self.configuration = configuration
super.init(frame: .zero)
configureSubviews()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configureSubviews() {
guard let config = configuration as? CustomListContentConfiguration else { return }
let leadingView = UIView()
leadingView.backgroundColor = .systemRed
let textField = UITextField()
textField.placeholder = config.placeholder
textField.font = .systemFont(ofSize: 100)
addSubview(leadingView)
addSubview(textField)
leadingView.translatesAutoresizingMaskIntoConstraints = false
textField.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
leadingView.centerYAnchor.constraint(equalTo: layoutMarginsGuide.centerYAnchor),
leadingView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor),
leadingView.widthAnchor.constraint(equalTo: layoutMarginsGuide.heightAnchor),
leadingView.heightAnchor.constraint(equalTo: layoutMarginsGuide.heightAnchor),
textField.topAnchor.constraint(equalTo: topAnchor),
textField.bottomAnchor.constraint(equalTo: bottomAnchor),
textField.leadingAnchor.constraint(equalTo: leadingView.trailingAnchor, constant: 16),
textField.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor),
textField.heightAnchor.constraint(greaterThanOrEqualToConstant: 44)
])
}
}