You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Compositional Layout实现内嵌3个UICollectionView的动态UICollectionViewCell

动态嵌套UICollectionView布局问题

我尝试使用UICollectionView Compositional Layout实现动态高度单元格,目前的视图层级如下:

  • UICollectionView (Compositional Layout)
    • UICollectionViewCell
      • innerStackView (垂直UIStack) // 包含帖子描述和「来自利雅得」标签
      • ...
      • UICollectionView (Compositional Layout)
      • UICollectionView (Flow Layout - 横向滚动)
      • UICollectionView(Compositional Layout)

已尝试的实现方案

  • 将内部集合视图添加到垂直堆栈视图中
  • 将内部集合视图添加到容器视图中通过约束布局

现有代码

外层UICollectionView(Compositional Layout)布局代码

func setupCollectionViewLayout() -> UICollectionViewLayout {
    let size = NSCollectionLayoutSize(
        widthDimension: NSCollectionLayoutDimension.fractionalWidth(1),
        heightDimension: NSCollectionLayoutDimension.estimated(300)
    )
    
    let item = NSCollectionLayoutItem(layoutSize: size)
    let group = NSCollectionLayoutGroup.vertical(layoutSize: size, subitem: item, count: 1)
    
    let section = NSCollectionLayoutSection(group: group)
    section.contentInsets = .zero
    section.interGroupSpacing = 0
    
    let headerFooterSize = NSCollectionLayoutSize(
        widthDimension: .fractionalWidth(1.0),
        heightDimension: .fractionalHeight(0.3)
    )
    
    let sectionHeader = NSCollectionLayoutBoundarySupplementaryItem(
        layoutSize: headerFooterSize,
        elementKind: UICollectionView.elementKindSectionHeader,
        alignment: .top
    )
            
    section.boundarySupplementaryItems = [sectionHeader]
    return UICollectionViewCompositionalLayout(section: section)
}

方案1:基于容器视图约束实现的Cell代码

class FeedsCell: UICollectionViewCell {

@IBOutlet weak var containerView: UIView!
@IBOutlet weak var innerStackView: UIStackView!
@IBOutlet weak var postDescription: UILabel!

var imageGalleryCollectionView: UICollectionView!
var reactionCollectionView: UICollectionView!
var commentCollectionView: UICollectionView!
    
override func awakeFromNib() {
    super.awakeFromNib()
}

func with(viewModel: FeedsCellViewModel) -> Self {
    postDescription.text = ".random(in: 0...255)"
            
    if !viewModel.imageProvider.dataSource.isEmpty {
        let imageLayout = setupImageLayout()
        
        imageGalleryCollectionView = UICollectionView(frame: .zero, collectionViewLayout: imageLayout)
        imageGalleryCollectionView.delegate = viewModel.imageProvider
        imageGalleryCollectionView.dataSource = viewModel.imageProvider
        
        containerView.addSubview(imageGalleryCollectionView)
        
    }
    
    if !viewModel.reactionProvider.dataSource.isEmpty {
        let reactionLayout = UICollectionViewFlowLayout()
        reactionLayout.scrollDirection = .horizontal
        
        reactionCollectionView = UICollectionView(frame: .zero, collectionViewLayout: reactionLayout)
        reactionCollectionView.showsHorizontalScrollIndicator = false
        reactionCollectionView.dataSource = viewModel.reactionProvider
        reactionCollectionView.delegate = viewModel.reactionProvider
        
        innerStackView.addArrangedSubview(reactionCollectionView)
        
        reactionCollectionView.anchor(heightConstant: 32)
    }
    
    if !viewModel.commentProvider.dataSource.isEmpty {
        let commentLayout = setupCommentLayout()
        
        commentCollectionView = UICollectionView(frame: .zero, collectionViewLayout: commentLayout)
        commentCollectionView.dataSource = viewModel.commentProvider
        commentCollectionView.delegate = viewModel.commentProvider
        
        containerView.addSubview(commentCollectionView)

        imageGalleryCollectionView.anchor(top: innerStackView.bottomAnchor, left: innerStackView.leftAnchor, bottom: commentCollectionView.topAnchor, right: innerStackView.rightAnchor)

        commentCollectionView.anchor(top: imageGalleryCollectionView.bottomAnchor, left: innerStackView.leftAnchor, bottom: containerView.bottomAnchor, right: innerStackView.rightAnchor)
                    
    }

    return self
}

override func prepareForReuse() {
    super.prepareForReuse()
    imageGalleryCollectionView?.removeFromSuperview()
    reactionCollectionView?.removeFromSuperview()
    commentCollectionView?.removeFromSuperview()
}

func setupCommentLayout() -> UICollectionViewCompositionalLayout {
    let layout = UICollectionViewCompositionalLayout.list(using: .init(appearance: .insetGrouped))
    return layout
}

func setupImageLayout() -> UICollectionViewCompositionalLayout {
    let mainItem = NSCollectionLayoutItem(
      layoutSize: NSCollectionLayoutSize(
        widthDimension: .fractionalWidth(2/3),
        heightDimension: .fractionalHeight(1.0)))

    mainItem.contentInsets = NSDirectionalEdgeInsets(
      top: 2,
      leading: 0,
      bottom: 0,
      trailing: 2)

    let pairItem = NSCollectionLayoutItem(
      layoutSize: NSCollectionLayoutSize(
        widthDimension: .fractionalWidth(1.0),
        heightDimension: .fractionalHeight(0.5)))

    pairItem.contentInsets = NSDirectionalEdgeInsets(
      top: 2,
      leading: 2,
      bottom: 0,
      trailing: 2)

    let trailingGroup = NSCollectionLayoutGroup.vertical(
      layoutSize: NSCollectionLayoutSize(
        widthDimension: .fractionalWidth(1/3),
        heightDimension: .fractionalHeight(1.0)),
      subitem: pairItem,
      count: 2)

    let mainWithPairGroup = NSCollectionLayoutGroup.horizontal(
      layoutSize: NSCollectionLayoutSize(
        widthDimension: .fractionalWidth(1.0),
        heightDimension: .fractionalHeight(4/9)),
      subitems: [mainItem, trailingGroup])

    let nestedGroup = NSCollectionLayoutGroup.vertical(
      layoutSize: NSCollectionLayoutSize(
        widthDimension: .fractionalWidth(1.0),
        heightDimension: .fractionalWidth(1.0)),
      subitems: [
        mainWithPairGroup,
      ]
    )

    let section = NSCollectionLayoutSection(group: nestedGroup)
    
    return UICollectionViewCompositionalLayout(section: section)
    
}
}

方案2:基于UIStackView实现的Cell代码

func with(viewModel: FeedsCellViewModel) -> Self {
    postDescription.text = ".random(in: 0...255)"
            
    if !viewModel.imageProvider.dataSource.isEmpty {
        let imageLayout = setupImageLayout()
        
        imageGalleryCollectionView = UICollectionView(frame: .zero, collectionViewLayout: imageLayout)
        imageGalleryCollectionView.delegate = viewModel.imageProvider
        imageGalleryCollectionView.dataSource = viewModel.imageProvider
        
        innerStackView.addArrangedSubview(imageGalleryCollectionView)
        
    }
    
    if !viewModel.reactionProvider.dataSource.isEmpty {
        let reactionLayout = UICollectionViewFlowLayout()
        reactionLayout.scrollDirection = .horizontal
        
        reactionCollectionView = UICollectionView(frame: .zero, collectionViewLayout: reactionLayout)
        reactionCollectionView.showsHorizontalScrollIndicator = false
        reactionCollectionView.dataSource = viewModel.reactionProvider
        reactionCollectionView.delegate = viewModel.reactionProvider
        
        innerStackView.addArrangedSubview(reactionCollectionView)
        
        reactionCollectionView.anchor(heightConstant: 32)
    }
    
    if !viewModel.commentProvider.dataSource.isEmpty {
        let commentLayout = setupCommentLayout()
        
        commentCollectionView = UICollectionView(frame: .zero, collectionViewLayout: commentLayout)
        commentCollectionView.dataSource = viewModel.commentProvider
        commentCollectionView.delegate = viewModel.commentProvider
        
        innerStackView.addArrangedSubview(commentCollectionView)

    }

    return self
}

问题诉求

两种方案均出现布局异常,经过5天调试仍未解决,需要正确实现该动态嵌套集合视图的布局方案。


内容的提问来源于stack exchange,提问作者Faisal

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.02 16:06:04