带有 groupPagingCentered 的 UICollectionViewCompositionalLayout 没有开始居中

UICollectionViewCompositionalLayout with groupPagingCentered doesn't start centered

我的布局代码非常简单,您会在每篇有关新组合布局的教程或文章中看到这些代码。

  func createLayout() -> UICollectionViewLayout {
    let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0))
    let item = NSCollectionLayoutItem(layoutSize: itemSize)
    item.contentInsets = .init(top: 0, leading: 5, bottom: 0, trailing: 5)

    let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.93), heightDimension: .fractionalHeight(1.0))
    let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])

    let section = NSCollectionLayoutSection(group: group)
    section.orthogonalScrollingBehavior = .groupPagingCentered

    let layout = UICollectionViewCompositionalLayout(section: section)
    return layout
  }

当我启动应用程序时,单元格未正确居中。只有当我将单元格拖动到最小量时,它才会 spring 到正确的位置。

之前:

稍微拖一下后:

我还没有在 SO 上看到任何关于相同问题的问题。 Twitter 或博客中没有人谈论它。不确定我在这里做错了什么?

这是预期的行为。 "centered" 表示单元格在滚动后 对齐中心。 在进行 any 滚动之前,整个组一直滚动到正确的。您的组只有 0.93 小数宽度,因此差异很小。当分数宽度较小时,效果不那么难看。

您可能想试试这个:

UPCarouselFlowLayout

易于实施并获得您需要的结果。

假设我要显示横幅,使用上面link就可以得到结果。

override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        DispatchQueue.main.async {
            let layout = self.collBannerView.collectionViewLayout as! UPCarouselFlowLayout
            layout.spacingMode = UPCarouselFlowLayoutSpacingMode.fixed(spacing: 10)
            //overlap(visibleOffset: 50)
            layout.itemSize = CGSize.init(width: self.view.frame.width - 70, height: 200)
            self.collBannerView.collectionViewLayout = layout
            let direction: UICollectionView.ScrollDirection = self.orientation.isPortrait ? .horizontal : .vertical
            layout.scrollDirection = direction
        }
    }

滚动到第一项:

@available(iOS 13.0, *)
private func centerCollectionView() {

    guard collectionView.collectionViewLayout is UICollectionViewCompositionalLayout else { return }
    collectionView.scrollToItem(at: IndexPath(item: 0, section: 0), at: .centeredHorizontally, animated: false)
}

如果该项目没有填满整个屏幕,您需要滚动到其他项目,否则会出现奇怪的闪烁。因此,例如,如果您的屏幕充满了两个项目,代码将是:

collectionView.scrollToItem(at: IndexPath(item: 1, section: 0), at: .centeredHorizontally, animated: false)
collectionView.scrollToItem(at: IndexPath(item: 0, section: 0), at: .centeredHorizontally, animated: false)

可能为时已晚,但这里有一个解决方法:

func createLayout() -> UICollectionViewLayout {
    let layout = UICollectionViewCompositionalLayout { (sectionIndex, environment) -> NSCollectionLayoutSection? in
        let sideInset: CGFloat = 5

        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0))
        let item = NSCollectionLayoutItem(layoutSize: itemSize)
        item.contentInsets = .init(top: 0, leading: sideInset, bottom: 0, trailing: sideInset)

        let groupWidth = environment.container.contentSize.width * 0.93
        let groupSize = NSCollectionLayoutSize(widthDimension: .absolute(groupWidth), heightDimension: .fractionalHeight(1.0))
        let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])

        let section = NSCollectionLayoutSection(group: group)

        // add leading and trailing insets to the section so groups are aligned to the center
        let sectionSideInset = (environment.container.contentSize.width - groupWidth) / 2
        section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: sectionSideInset, bottom: 0, trailing: sectionSideInset)

        // note this is not .groupPagingCentered
        section.orthogonalScrollingBehavior = .groupPaging

        return section
    }

    return layout
}

我发现,如果您将页眉添加到您的部分(甚至是空的),它就可以解决问题。

let layoutSectionHeaderSize = NSCollectionLayoutSize(widthDimension: .estimated(1.0), heightDimension: .estimated(1.0))
let layoutSectionHeader = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: layoutSectionHeaderSize, elementKind: UICollectionView.elementKindSectionHeader, alignment: .top)
section.boundarySupplementaryItems = [layoutSectionHeader]

是的,你需要这个:

register(SectionHeader.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "SectionHeader")

并在此处提供任何空 UICollectionReusableView

    func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
guard let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "SectionHeader", for: indexPath) as? SectionHeader else {
    fatalError("Could not dequeue SectionHeader")
   }
    return // some empty SectionHeader
}

工作样本是here

就我而言,我的 collectionView-embedded-viewController 嵌入在自定义标签栏控制器中。因此,当我的 collectionView 被滑动时,我向 parent viewController 的标签栏按钮发送了操作。它奏效了。

class ChildVC: UIViewController {
    var parentVC: MY_PARENT_VC?
    var collectionView: UICollectionView!
    
    // for the first horizontal row
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        parentVC?.MY_CUSTOM_BUTTON.sendActions(for: .touchUpInside)
    }
}

extension ChildVC: UICollectionViewDelegate {
    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        parentVC?.MY_CUSTOM_BUTTON.sendActions(for: .touchUpInside)
    }
}

轻松修复

正如另一个答案中所建议的,您可以告诉集合视图将某个项目滚动到中心。如果您在视图可见后执行此操作,用户将看到 UI 自行移动……不理想。您可以将项目居中代码放入 viewDidLoad 但它不会工作,除非您设置非常轻微的延迟 - 我假设您只需要在初始布局传递完成后将单元格居中。下面的代码对我有用:

override func viewDidLoad() {
    super.viewDidLoad()
    
    // collectionView setup goes here...
     
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.001) { [self] in
        collectionView.scrollToItem(at: IndexPath(item: 0, section: 0), at: .centeredHorizontally, animated: false)
    }
}

甚至后来,但我在尝试做类似的事情时遇到了类似的问题,这条线神奇地起作用了:

section.contentInsetsReference = .readableContent

我完全不知道它为什么有效。我尝试使用 item.contentInsets、edgeSpacing 等所有方法。神奇的是,这对我有用。也许你会取得类似的成功?