iOS 14 UICollectionView新功能之TableView

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS 14 UICollectionView新功能之TableView相关的知识,希望对你有一定的参考价值。

参考技术A 在 iOS14 中,用 CollectionView 实现一个 TableView 样式的列表非常简单。
使用UICollectionLayoutListConfiguration即可创建。

UICollectionLayoutListConfiguration
其中insetGrouped为样式。还有plain、grouped、insetGrouped、sidebar、sidebarPlain等样式。

iOS6 UICollectionView 和 UIPageControl - 如何获得可见单元格?

【中文标题】iOS6 UICollectionView 和 UIPageControl - 如何获得可见单元格?【英文标题】:iOS6 UICollectionView and UIPageControl - How to get visible cell? 【发布时间】:2012-11-01 11:44:13 【问题描述】:

在研究 iOS6 新功能时,我遇到了一个关于 UICollectionView 的问题。 我目前正在使用 Flow 布局测试它,并将滚动方向设置为水平,启用滚动和分页。我已将其大小设置为与我的自定义单元格完全相同,因此它可以一次显示一个,并且通过横向滚动它,用户将看到其他现有单元格。

效果很好。

现在我想在我创建的集合视图中添加和UIPageControl,这样它就可以显示哪个单元格是可见的以及那里有多少个单元格。

构建页面控件相当简单,定义了 frame 和 numberOfPages。

作为问号,我遇到的问题是如何获取当前在集合视图中可见的单元格,以便它可以更改页面控件的 currentPage。

我尝试过委托方法,例如 cellForItemAtIndexPath,但它是用来加载单元格的,而不是显示它们。 didEndDisplayingCell 当一个单元格不再显示时触发,这与我需要的相反。

似乎-visibleCells 和-indexPathsForVisibleItems,集合视图方法,对我来说是正确的选择,但我遇到了另一个问题。什么时候触发?

在此先感谢,希望我说得足够清楚,以便你们能够理解我!

【问题讨论】:

【参考方案1】:

您必须将自己设置为 UIScrollViewDelegate 并像这样实现 scrollViewDidEndDecelerating: 方法:

Objective-C

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView

    CGFloat pageWidth = self.collectionView.frame.size.width;
    self.pageControl.currentPage = self.collectionView.contentOffset.x / pageWidth;

斯威夫特

func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) 

    let pageWidth = self.collectionView.frame.size.width
    pageControl.currentPage = Int(self.collectionView.contentOffset.x / pageWidth)

【讨论】:

无需为 CollectionView 创建 IVAR:- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView CGFloat pageWidth = _tutorialCollectionViewFrame.size.width; _pageControl.currentPage = scrollView.contentOffset.x / pageWidth; 是的,这是正确的答案...: func scrollViewDidEndDecelerating(scrollView: UIScrollView) let pageWidth: CGFloat = self.collectionView.frame.size.width self.pageControl.currentPage = Int(self.collectionView .contentOffset.x / pageWidth) 我发现使用 scrollViewDidScroll 会更顺畅,因为理论上您可以在没有 pageControl 更新的情况下左右滚动,直到它停止减速。我的代码如下所示: func scrollViewDidScroll(_ scrollView: UIScrollView) let page = Int(round(self.collectionView.contentOffset.x / self.collectionView.frame.size.width)) if self.pageControl.currentPage != page self.pageControl.currentPage = page 【参考方案2】:

我也为此苦苦挣扎了一段时间,然后我被建议检查 UICollectionView 的父类。其中一个恰好是 UIScrollView,如果您将自己设置为 UIScrollViewDelegate,您可以访问非常有用的方法,例如 scrollViewDidEndDecelerating,这是一个更新 UIPageControl 的好地方。

【讨论】:

我向我的 UIViewController 添加了委托,但仍然没有调用该方法。有什么想法吗? @Dejel 记得设置滚动视图的委托。【参考方案3】:

我建议对计算和处理进行一些调整,因为它会在任何滚动位置立即更新页面控件,并具有更高的准确性。

以下解决方案适用于任何滚动视图或其子类(UITableView UICollectionView 和其他)

在 viewDidLoad 方法中写这个

scrollView.delegate = self

然后使用您的语言的代码:

斯威夫特 3

func scrollViewDidScroll(_ scrollView: UIScrollView) 
    
       let pageWidth = scrollView.frame.width
       pageControl.currentPage = Int((scrollView.contentOffset.x + pageWidth / 2) / pageWidth)
    

斯威夫特 2:

func scrollViewDidScroll(scrollView: UIScrollView) 

   let pageWidth = CGRectGetWidth(scrollView.frame)
   pageControl.currentPage = Int((scrollView.contentOffset.x + pageWidth / 2) / pageWidth)

目标 C

- (void)scrollViewDidScroll:(UIScrollView *)scrollView

    CGFloat pageWidth = self.collectionView.frame.size.width;
    self.pageControl.currentPage = (self.collectionView.contentOffset.x + pageWidth / 2) / pageWidth;

【讨论】:

您的计算更好,但-[scrollViewDidEndDecelerating:] 更适合此代码。 我认为这取决于您的口味。 Apple 在滚动完成后更新 pageControl,但对我来说,更频繁地更新页面控件对用户来说看起来更好。如果不是拖慢性能可以使用这个方法。 在这种情况下,我看到在点击UIPageControl 的左右部分时闪烁效果不佳。我的意思是通过UIPageControl点击更改页面。【参考方案4】:

另一个代码更少的选项是使用可见的项目索引路径并设置页面控件。

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView 
    self.pageControl.currentPage = [[[self.collectionView indexPathsForVisibleItems] firstObject] row];

【讨论】:

【参考方案5】:
    PageControl 放在您的视图中或由代码设置。 设置UIScrollViewDelegateCollectionview-> cellForItemAtIndexPath(方法)中添加以下内容 计算页数的代码,

int 页面 =floor(ImageCollectionView.contentSize.width/ImageCollectionView.frame.size.width); [pageControl setNumberOfPages:pages];

添加ScrollView Delegate方法,

pragma mark - UIPageControl 的 UIScrollVewDelegate

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView

    CGFloat pageWidth = ImageCollectionView.frame.size.width;
    float currentPage = ImageCollectionView.contentOffset.x / pageWidth;

    if (0.0f != fmodf(currentPage, 1.0f))
    
        pageControl.currentPage = currentPage + 1;
    
    else
    
        pageControl.currentPage = currentPage;
    
    NSLog(@"finishPage: %ld", (long)pageControl.currentPage);

【讨论】:

【参考方案6】:

我知道这是一个旧的,但我只需要再次实现这种功能,并且需要添加一些内容来提供更完整的答案。

首先:使用scrollViewDidEndDecelerating 假设用户在拖动时抬起手指(更像是轻弹动作),因此存在减速阶段。如果用户在没有抬起手指的情况下拖动,UIPageControl 仍将指示拖动开始之前的旧页面。相反,使用scrollViewDidScroll 回调意味着视图在拖动和轻弹之后以及在拖动和滚动期间都会更新,因此对于用户来说感觉更加灵敏和准确。

其次:依靠pagewidth 计算所选索引假定所有单元格具有相同的宽度并且每个屏幕有一个单元格。利用UICollectionView 上的indexPathForItemAtPoint 方法可提供更具弹性的结果,适用于不同的布局和单元格大小。下面的实现假设框架的中心是要在pagecontrol 中表示的所需单元格。此外,如果存在单元间间距,则在滚动期间有时 selectedIndex 可能为 nil 或可选,因此需要在 pageControl 上设置之前检查并展开。

 func scrollViewDidScroll(scrollView: UIScrollView) 
   let contentOffset = scrollView.contentOffset
   let centrePoint =  CGPointMake(
    contentOffset.x + CGRectGetMidX(scrollView.frame),
    contentOffset.y + CGRectGetMidY(scrollView.frame)
   )
   if let index = self.collectionView.indexPathForItemAtPoint(centrePoint)
     self.pageControl.currentPage = index.row
   
 

还有一件事 - 在UIPageControl 上设置页数,如下所示:

  func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int 
    self.pageControl.numberOfPages = 20
    return self.pageControl.numberOfPages
   

【讨论】:

【参考方案7】:

简单的斯威夫特

public func scrollViewDidEndDecelerating(scrollView: UIScrollView) 
    pageControl.currentPage = (collectionView.indexPathsForVisibleItems().first?.row)!

如果你实现UICollectionViewDelegateUIScrollViewDelegate已经实现了@

【讨论】:

我发现接受的答案效果更好,因为它占据了滚动视图中间的行。【参考方案8】:

如果使用scrollViewDidScroll,则应手动更新页面控件以⚠️ 避免点击页面控件时闪烁的点

设置UIPageControl

let pageControl = UIPageControl()
pageControl.pageIndicatorTintColor = .label
pageControl.defersCurrentPageDisplay = true // Opt-out from automatic display
pageControl.numberOfPages = viewModel.items.count
pageControl.addTarget(self, action: #selector(pageControlValueChanged), for: .valueChanged)

实施操作(使用下面的扩展)。

@objc func pageControlValueChanged(_ sender: UIPageControl) 
    collectionView.scroll(to: sender.currentPage)

在每次滚动时手动更新UIPageControl

extension ViewController: UIScrollViewDelegate 
        
    func scrollViewDidScroll(_ scrollView: UIScrollView) 
        pageControl.currentPage = collectionView.currentPage
        pageControl.updateCurrentPageDisplay() // Display only here
    


方便的UICollectionView 扩展。

extension CGRect 
    
    var middle: CGPoint 
        CGPoint(x: midX, y: midY)
    


extension UICollectionView 
    
    var visibleArea: CGRect 
        CGRect(origin: contentOffset, size: bounds.size)
    
    
    var currentPage: Int 
        indexPathForItem(at: visibleArea.middle)?.row ?? 0
    
    
    func scroll(to page: Int) 
        scrollToItem(
            at: IndexPath(row: page, section: 0),
            at: .centeredHorizontally,
            animated: true
        )
    

【讨论】:

以上是关于iOS 14 UICollectionView新功能之TableView的主要内容,如果未能解决你的问题,请参考以下文章

iOS 14 上的 UICollectionView 列表布局问题

iOS14新功能分享介绍

iOS14 中 UICollectionview 的潜在错误

当我在 iOS 14 上的 UITableView 单元格内使用 UICollectionView 时,不会调用 UICollectionView cellForItemAt

iOS 14 来了,新功能细节评测,抢鲜体验

iOS 14 即将上线两个新功能