// // HomeViewController.swift // Test // // Created by yunhao on 2022/12/3. // import UIKit // MARK: - Home View Controller class HomeViewController: UICollectionViewController { init() { let configuration = UICollectionLayoutListConfiguration(appearance: .insetGrouped) let collectionViewLayout = UICollectionViewCompositionalLayout.list(using: configuration) super.init(collectionViewLayout: collectionViewLayout) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() collectionView.register(TextLabelCell.self, forCellWithReuseIdentifier: "\(TextLabelCell.self)") collectionView.register(ConfigurationTextLabelCell.self, forCellWithReuseIdentifier: "\(ConfigurationTextLabelCell.self)") } override func numberOfSections(in collectionView: UICollectionView) -> Int { return 2 } override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return 1 } override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { if indexPath.section == 0 { // Configure the cell in transitional way. let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "\(TextLabelCell.self)", for: indexPath) as! TextLabelCell cell.textLabel.text = "\(indexPath)" return cell } else { // Configure the cell with new content configuration api. let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "\(ConfigurationTextLabelCell.self)", for: indexPath) as! ConfigurationTextLabelCell var content = cell.defaltTextLabelContent() content.text = "\(indexPath)" cell.contentConfiguration = content cell.setNeedsUpdateConfiguration() return cell } } } // MARK: - Tranditional Cell class TextLabelCell: UICollectionViewListCell { let hStackView = UIStackView() let textLabel = UILabel() override init(frame: CGRect) { super.init(frame: frame) contentView.addSubview(hStackView) hStackView.addArrangedSubview(textLabel) hStackView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ hStackView.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor), hStackView.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor), hStackView.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor), hStackView.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor), ]) hStackView.axis = .horizontal // FIXME: When alignment is set to `.center`, the stack view can't calculate it's height correctly. It's height is a little larger than it's content. hStackView.alignment = .center // The default `.fill` alignment is fine. // hStackView.alignment = .fill hStackView.strokeLine(width: 1, color: .blue) textLabel.strokeLine(width: 2, color: .red) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } // MARK: - Content Configuration Cell class ConfigurationTextLabelCell: UICollectionViewListCell { func defaltTextLabelContent() -> TextLabelContentView.Configuration { return TextLabelContentView.Configuration() } } class TextLabelContentView: UIView, UIContentView { struct Configuration: UIContentConfiguration { var text: String? = "AAA" func makeContentView() -> UIView & UIContentView { return TextLabelContentView(configuration: self) } func updated(for state: UIConfigurationState) -> TextLabelContentView.Configuration { return self } } let hStackView = UIStackView() let textLabel = UILabel() var configuration: UIContentConfiguration { didSet { textLabel.text = (configuration as? Configuration)?.text } } func supports(_ configuration: UIContentConfiguration) -> Bool { return true } init(configuration: UIContentConfiguration) { self.configuration = configuration super.init(frame: .zero) addSubview(hStackView) hStackView.addArrangedSubview(textLabel) hStackView.translatesAutoresizingMaskIntoConstraints = false directionalLayoutMargins = NSDirectionalEdgeInsets(top: 8, leading: 20, bottom: 8, trailing: 20) NSLayoutConstraint.activate([ hStackView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor), hStackView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor), hStackView.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor), hStackView.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor), ]) hStackView.axis = .horizontal enum StackViewLayout { case alignmentFill case alignmentCenterWithOneSubview case alignmentCenterWithTwoSubview } // Change this value to see different behaviors. let layout: StackViewLayout = .alignmentCenterWithOneSubview switch layout { case .alignmentFill: hStackView.alignment = .fill case .alignmentCenterWithOneSubview: // FIXME: When the stack view's alignment is `.center` and it has only one arranged subview, This line will cause a crash: // *** Assertion failure in void _UIContentViewAssertValidFittingSize(UIView * _Nonnull __strong, CGSize, UILayoutPriority, UILayoutPriority)(), _UIContentViewShared.h:66 // Thread 1: "Content view returned an invalid size {353, 1.7976931348623157e+308} from -systemLayoutSizeFittingSize:withHorizontalFittingPriority:verticalFittingPriority: which is not allowed. If you have implemented a custom content view, you need to add constraints inside it so that its size is not ambiguous, or you need to manually compute and return a valid size. Content view: ; layer = >" hStackView.alignment = .center case .alignmentCenterWithTwoSubview: // When the stack view's alignment is `.center` and it has more than one arranged subviews, it works fine. hStackView.alignment = .center hStackView.addArrangedSubview(UIView()) } hStackView.strokeLine(width: 1, color: .blue) textLabel.strokeLine(width: 2, color: .red) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } // MARK: - Debug Helper extension UIView { func strokeLine(width: CGFloat, color: UIColor) { layer.borderColor = color.cgColor layer.borderWidth = width } }