UICollectionView CompositionalLayout 不调用 UIScrollDelegate
UICollectionView CompositionalLayout not calling UIScrollDelegate
这是我的问题:
1。一旦 UICollectionView 实现了 compositionalLayout,ScrollViewDelegate 就不会被调用。
使用 flowLayout 和 UICollectionViewDataSource 调用 scrollView 委托。一旦我实现了 diffable 数据源和 CompositionalLayout,scrollView 委托就不会再被调用了。
2。 collectionView.decelerationRate = .fast 在实现 CompositionalLayout
时被忽略
我的理解是 UICollectionViewDelegate 应该调用 UIScrollViewDelegate,我看了很远但没有运气。有人可以指出我错过了什么吗?我是不是把 compositionalLayout 整合错了?
这是我的代码:
import UIKit
class ViewController: UIViewController, UICollectionViewDelegate, UIScrollViewDelegate {
enum Section {
case weekdayHeader
}
@IBOutlet weak var collectionView: UICollectionView!
let weekdays = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday","Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
var weekdaysArr = [Weekdays]()
var dataSource: UICollectionViewDiffableDataSource<Section, Weekdays>! = nil
private let cellReuseIdentifier = "myCell"
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView.delegate = self
self.collectionView.register(UINib(nibName: "MyCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: cellReuseIdentifier)
self.collectionView.decelerationRate = .normal
configureCollectionView()
configureDataSource()
configureWeekdays()
updateCollectionView()
// Do any additional setup after loading the view.
}
func configureCollectionView(){
self.collectionView.delegate = self
self.collectionView.collectionViewLayout = generateLayout()
}
func generateLayout() -> UICollectionViewLayout {
let itemSize = NSCollectionLayoutSize(
widthDimension: .estimated(10),
heightDimension: .fractionalHeight(1))
let weekdayItem = NSCollectionLayoutItem(layoutSize: itemSize)
// weekdayItem.contentInsets = NSDirectionalEdgeInsets(top: 5, leading: 50, bottom: 5, trailing: 50)
let groupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(0.3),
heightDimension: .fractionalHeight(1.0))
let group = NSCollectionLayoutGroup.vertical(layoutSize: groupSize, subitems: [weekdayItem])
// let spacing = CGFloat(10)
// group.interItemSpacing = .fixed(spacing)
let section = NSCollectionLayoutSection(group: group)
section.interGroupSpacing = CGFloat(20)
section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 500)
section.orthogonalScrollingBehavior = .continuousGroupLeadingBoundary
let layout = UICollectionViewCompositionalLayout(section: section)
return layout
}
func configureDataSource() {
dataSource = UICollectionViewDiffableDataSource
<Section, Weekdays>(collectionView: collectionView)
{ (collectionView: UICollectionView, indexPath: IndexPath, weekday: Weekdays) -> UICollectionViewCell? in
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: self.cellReuseIdentifier, for: indexPath) as? MyCollectionViewCell else {
fatalError("Cannot create a new cell") }
cell.titleLabel.text = weekday.name
print("weekday.name = \(weekday.name)")
return cell
}
// let snapshot = snapshotForCurrentState()
// dataSource.apply(snapshot, animatingDifferences: false)
}
func configureWeekdays(){
weekdaysArr.append(Weekdays(name: "Monday"))
weekdaysArr.append(Weekdays(name: "Tuesday"))
weekdaysArr.append(Weekdays(name: "Wednesday"))
weekdaysArr.append(Weekdays(name: "Thursday"))
weekdaysArr.append(Weekdays(name: "Friday"))
weekdaysArr.append(Weekdays(name: "Saturday"))
weekdaysArr.append(Weekdays(name: "Sunday"))
}
func updateCollectionView(){
var snapshot = NSDiffableDataSourceSnapshot<Section, Weekdays>()
snapshot.appendSections([.weekdayHeader])
snapshot.appendItems(weekdaysArr, toSection: .weekdayHeader)
dataSource.apply(snapshot, animatingDifferences: false)
}
//MARK: Scroll View Delegate
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print("ScrollView decel rate: \(scrollView.decelerationRate)")
}
public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
print("Begin")
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
print("End")
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
print("END")
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("Did select a cell here")
}
}
struct Weekdays: Hashable {
let identifier: UUID = UUID()
let name: String
func hash(into hasher: inout Hasher){
return hasher.combine(identifier)
}
static func == (lhs: Weekdays, rhs: Weekdays) -> Bool {
return lhs.identifier == rhs.identifier
}
}
谢谢
编辑:更新代码 -
编辑:与来自 WWDC 的 Apple 示例代码进行比较,我发现了这种行为。
如果我使用示例代码网格布局
func gridLayout() -> UICollectionViewLayout {
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.2),
heightDimension: .fractionalHeight(1.0))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalWidth(0.2))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize,
subitems: [item])
let section = NSCollectionLayoutSection(group: group)
let layout = UICollectionViewCompositionalLayout(section: section)
return layout
}
当只有 7 个项目时,Scroll Delegate 方法永远不会被调用。
我仍然可以滚动,预期的行为应该是滚动委托方法确实被调用,因为我有垂直弹跳。
当有大约 40 个项目并且内容明显超出屏幕尺寸时,滚动委托会被调用。
加载我自己的布局时有趣的是:
func generateLayout() -> UICollectionViewLayout {
let itemSize = NSCollectionLayoutSize(
widthDimension: .estimated(10),
heightDimension: .fractionalHeight(1))
let weekdayItem = NSCollectionLayoutItem(layoutSize: itemSize)
// weekdayItem.contentInsets = NSDirectionalEdgeInsets(top: 5, leading: 50, bottom: 5, trailing: 50)
let groupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(0.3),
heightDimension: .fractionalHeight(1.0))
let group = NSCollectionLayoutGroup.vertical(layoutSize: groupSize, subitems: [weekdayItem])
// let spacing = CGFloat(10)
// group.interItemSpacing = .fixed(spacing)
let section = NSCollectionLayoutSection(group: group)
section.interGroupSpacing = CGFloat(20)
section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 500)
section.orthogonalScrollingBehavior = .continuousGroupLeadingBoundary
let layout = UICollectionViewCompositionalLayout(section: section)
return layout
}
Scroll 委托永远不会被调用。即使有 40 多个项目。
关于如何强制 UICollectionView 与其 ScrollViewDelegates 通信的任何想法?
似乎是 UICollectionView 组合布局中水平滚动组的意外行为
以防其他人遇到同样的问题。这似乎是水平滚动组合布局组的问题。
只有垂直滚动组会触发 UIScrollViewDelegates。
不幸的是,这也意味着似乎decelerationrate.fast不能应用于水平滚动。它只会被忽略。
重现问题的步骤和分析
可以通过在此处的 wwdc 示例代码 "Advancements in Collection View Layout" 中实施 UIScrollViewDelegate 方法来重新创建此行为:https://developer.apple.com/videos/play/wwdc2019/215/
在 class: OrthogonalScrollBehaviorViewController.swift 中,我们找到水平和垂直滚动组。
结论
UIScrollViewDelegate 只与垂直滚动组交互。
水平滚动组不与集合视图的滚动委托通信。
如果在水平滚动组中需要滚动委托方法,那么仍然需要使用嵌套 CollectionView 和 FlowLayout/自定义布局的传统方法。
备注
如果有人能向我指出我遗漏了什么,我将非常感激,直到那时这将作为我上述问题的答案。
感谢所有发表评论的人。
答案是在 (NSCollectionLayoutSection
) 部分使用 visibleItemsInvalidationHandler
闭包。
只要事件导致动画,此处理程序就会收到更新。它的操作类似于 scrollViewDidScroll
并且在水平滚动组上它将传递所有项目、滚动视图偏移量和 NSCollectionLayoutEnvironment。
示例:
section.visibleItemsInvalidationHandler = { [weak self] visibleItems, point, environment in
self?.currentIndex = visibleItems.last?.indexPath.row
}
代码片段:
section.visibleItemsInvalidationHandler = { [weak self] visibleItems, point, environment in
self?.pager.currentPage = visibleItems.last!.indexPath.row
}
根据您的用例,您还可以使用集合视图委托方法
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath)
func collectionView(_ collectionView: UICollectionView, didEndDisplayingCell cell: UICollectionViewCell, forItemAt indexPath: IndexPath)
在这些委托方法中 - 您可以在特定部分即将显示单元格时做出反应以结束显示单元格
如果您垂直滚动 collectionView,UICollectionViewDelegate 将调用 scrollDidView。在我的情况下,我试图从水平滚动进行跟踪,我正在使用以下代码来使事情正常进行。
func collectionView(_ collectionView: UICollectionView, didEndDisplayingCell cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
let visibleRect = CGRect(origin: yourCollectionView.contentOffset, size: photoCollectionView.bounds.size)
let visiblePoint = CGPoint(x: visibleRect.midX, y: visibleRect.midY)
let visibleIndexPath = yourCollectionView.indexPathForItem(at: visiblePoint) // Current display cell in collectionView
}
这不是这个问题的答案,但希望这对在 collectionView 组合布局中从水平滚动中搜索跟踪当前显示单元格的人有所帮助
我找到了一种方便的方法来处理这个问题,您可以避免设置正交滚动并使用配置来代替:
let config = UICollectionViewCompositionalLayoutConfiguration()
config.scrollDirection = .horizontal
let layout = UICollectionViewCompositionalLayout(sectionProvider:sectionProvider,configuration: config)
这将调用 collectionview
的所有滚动委托。希望这会对某人有所帮助。
这是我的问题:
1。一旦 UICollectionView 实现了 compositionalLayout,ScrollViewDelegate 就不会被调用。
使用 flowLayout 和 UICollectionViewDataSource 调用 scrollView 委托。一旦我实现了 diffable 数据源和 CompositionalLayout,scrollView 委托就不会再被调用了。
2。 collectionView.decelerationRate = .fast 在实现 CompositionalLayout
时被忽略我的理解是 UICollectionViewDelegate 应该调用 UIScrollViewDelegate,我看了很远但没有运气。有人可以指出我错过了什么吗?我是不是把 compositionalLayout 整合错了?
这是我的代码:
import UIKit
class ViewController: UIViewController, UICollectionViewDelegate, UIScrollViewDelegate {
enum Section {
case weekdayHeader
}
@IBOutlet weak var collectionView: UICollectionView!
let weekdays = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday","Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
var weekdaysArr = [Weekdays]()
var dataSource: UICollectionViewDiffableDataSource<Section, Weekdays>! = nil
private let cellReuseIdentifier = "myCell"
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView.delegate = self
self.collectionView.register(UINib(nibName: "MyCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: cellReuseIdentifier)
self.collectionView.decelerationRate = .normal
configureCollectionView()
configureDataSource()
configureWeekdays()
updateCollectionView()
// Do any additional setup after loading the view.
}
func configureCollectionView(){
self.collectionView.delegate = self
self.collectionView.collectionViewLayout = generateLayout()
}
func generateLayout() -> UICollectionViewLayout {
let itemSize = NSCollectionLayoutSize(
widthDimension: .estimated(10),
heightDimension: .fractionalHeight(1))
let weekdayItem = NSCollectionLayoutItem(layoutSize: itemSize)
// weekdayItem.contentInsets = NSDirectionalEdgeInsets(top: 5, leading: 50, bottom: 5, trailing: 50)
let groupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(0.3),
heightDimension: .fractionalHeight(1.0))
let group = NSCollectionLayoutGroup.vertical(layoutSize: groupSize, subitems: [weekdayItem])
// let spacing = CGFloat(10)
// group.interItemSpacing = .fixed(spacing)
let section = NSCollectionLayoutSection(group: group)
section.interGroupSpacing = CGFloat(20)
section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 500)
section.orthogonalScrollingBehavior = .continuousGroupLeadingBoundary
let layout = UICollectionViewCompositionalLayout(section: section)
return layout
}
func configureDataSource() {
dataSource = UICollectionViewDiffableDataSource
<Section, Weekdays>(collectionView: collectionView)
{ (collectionView: UICollectionView, indexPath: IndexPath, weekday: Weekdays) -> UICollectionViewCell? in
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: self.cellReuseIdentifier, for: indexPath) as? MyCollectionViewCell else {
fatalError("Cannot create a new cell") }
cell.titleLabel.text = weekday.name
print("weekday.name = \(weekday.name)")
return cell
}
// let snapshot = snapshotForCurrentState()
// dataSource.apply(snapshot, animatingDifferences: false)
}
func configureWeekdays(){
weekdaysArr.append(Weekdays(name: "Monday"))
weekdaysArr.append(Weekdays(name: "Tuesday"))
weekdaysArr.append(Weekdays(name: "Wednesday"))
weekdaysArr.append(Weekdays(name: "Thursday"))
weekdaysArr.append(Weekdays(name: "Friday"))
weekdaysArr.append(Weekdays(name: "Saturday"))
weekdaysArr.append(Weekdays(name: "Sunday"))
}
func updateCollectionView(){
var snapshot = NSDiffableDataSourceSnapshot<Section, Weekdays>()
snapshot.appendSections([.weekdayHeader])
snapshot.appendItems(weekdaysArr, toSection: .weekdayHeader)
dataSource.apply(snapshot, animatingDifferences: false)
}
//MARK: Scroll View Delegate
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print("ScrollView decel rate: \(scrollView.decelerationRate)")
}
public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
print("Begin")
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
print("End")
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
print("END")
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("Did select a cell here")
}
}
struct Weekdays: Hashable {
let identifier: UUID = UUID()
let name: String
func hash(into hasher: inout Hasher){
return hasher.combine(identifier)
}
static func == (lhs: Weekdays, rhs: Weekdays) -> Bool {
return lhs.identifier == rhs.identifier
}
}
谢谢
编辑:更新代码 -
编辑:与来自 WWDC 的 Apple 示例代码进行比较,我发现了这种行为。
如果我使用示例代码网格布局
func gridLayout() -> UICollectionViewLayout {
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.2),
heightDimension: .fractionalHeight(1.0))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalWidth(0.2))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize,
subitems: [item])
let section = NSCollectionLayoutSection(group: group)
let layout = UICollectionViewCompositionalLayout(section: section)
return layout
}
当只有 7 个项目时,Scroll Delegate 方法永远不会被调用。
我仍然可以滚动,预期的行为应该是滚动委托方法确实被调用,因为我有垂直弹跳。
当有大约 40 个项目并且内容明显超出屏幕尺寸时,滚动委托会被调用。
加载我自己的布局时有趣的是:
func generateLayout() -> UICollectionViewLayout {
let itemSize = NSCollectionLayoutSize(
widthDimension: .estimated(10),
heightDimension: .fractionalHeight(1))
let weekdayItem = NSCollectionLayoutItem(layoutSize: itemSize)
// weekdayItem.contentInsets = NSDirectionalEdgeInsets(top: 5, leading: 50, bottom: 5, trailing: 50)
let groupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(0.3),
heightDimension: .fractionalHeight(1.0))
let group = NSCollectionLayoutGroup.vertical(layoutSize: groupSize, subitems: [weekdayItem])
// let spacing = CGFloat(10)
// group.interItemSpacing = .fixed(spacing)
let section = NSCollectionLayoutSection(group: group)
section.interGroupSpacing = CGFloat(20)
section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 500)
section.orthogonalScrollingBehavior = .continuousGroupLeadingBoundary
let layout = UICollectionViewCompositionalLayout(section: section)
return layout
}
Scroll 委托永远不会被调用。即使有 40 多个项目。
关于如何强制 UICollectionView 与其 ScrollViewDelegates 通信的任何想法?
似乎是 UICollectionView 组合布局中水平滚动组的意外行为
以防其他人遇到同样的问题。这似乎是水平滚动组合布局组的问题。
只有垂直滚动组会触发 UIScrollViewDelegates。
不幸的是,这也意味着似乎decelerationrate.fast不能应用于水平滚动。它只会被忽略。
重现问题的步骤和分析
可以通过在此处的 wwdc 示例代码 "Advancements in Collection View Layout" 中实施 UIScrollViewDelegate 方法来重新创建此行为:https://developer.apple.com/videos/play/wwdc2019/215/ 在 class: OrthogonalScrollBehaviorViewController.swift 中,我们找到水平和垂直滚动组。
结论
UIScrollViewDelegate 只与垂直滚动组交互。 水平滚动组不与集合视图的滚动委托通信。
如果在水平滚动组中需要滚动委托方法,那么仍然需要使用嵌套 CollectionView 和 FlowLayout/自定义布局的传统方法。
备注
如果有人能向我指出我遗漏了什么,我将非常感激,直到那时这将作为我上述问题的答案。
感谢所有发表评论的人。
答案是在 (NSCollectionLayoutSection
) 部分使用 visibleItemsInvalidationHandler
闭包。
只要事件导致动画,此处理程序就会收到更新。它的操作类似于 scrollViewDidScroll
并且在水平滚动组上它将传递所有项目、滚动视图偏移量和 NSCollectionLayoutEnvironment。
示例:
section.visibleItemsInvalidationHandler = { [weak self] visibleItems, point, environment in
self?.currentIndex = visibleItems.last?.indexPath.row
}
代码片段:
section.visibleItemsInvalidationHandler = { [weak self] visibleItems, point, environment in
self?.pager.currentPage = visibleItems.last!.indexPath.row
}
根据您的用例,您还可以使用集合视图委托方法
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath)
func collectionView(_ collectionView: UICollectionView, didEndDisplayingCell cell: UICollectionViewCell, forItemAt indexPath: IndexPath)
在这些委托方法中 - 您可以在特定部分即将显示单元格时做出反应以结束显示单元格
UICollectionViewDelegate 将调用 scrollDidView。在我的情况下,我试图从水平滚动进行跟踪,我正在使用以下代码来使事情正常进行。
func collectionView(_ collectionView: UICollectionView, didEndDisplayingCell cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
let visibleRect = CGRect(origin: yourCollectionView.contentOffset, size: photoCollectionView.bounds.size)
let visiblePoint = CGPoint(x: visibleRect.midX, y: visibleRect.midY)
let visibleIndexPath = yourCollectionView.indexPathForItem(at: visiblePoint) // Current display cell in collectionView
}
这不是这个问题的答案,但希望这对在 collectionView 组合布局中从水平滚动中搜索跟踪当前显示单元格的人有所帮助
我找到了一种方便的方法来处理这个问题,您可以避免设置正交滚动并使用配置来代替:
let config = UICollectionViewCompositionalLayoutConfiguration()
config.scrollDirection = .horizontal
let layout = UICollectionViewCompositionalLayout(sectionProvider:sectionProvider,configuration: config)
这将调用 collectionview
的所有滚动委托。希望这会对某人有所帮助。