UICollectionView 不自动滚动

Posted

技术标签:

【中文标题】UICollectionView 不自动滚动【英文标题】:UICollectionView not auto scrolling 【发布时间】:2020-10-05 09:16:51 【问题描述】:

所以,我有一个用于自动滚动 UICollectionView 的代码(启用分页的水平)。它正在工作。现在,我更新了 Xcode 和 ios,突然,相同的代码不再工作了。我真的没有改变任何其他东西。其他人也遇到过这个问题吗? 此集合视图位于标题可重用视图内。

func setTimer() 
        let _ = Timer.scheduledTimer(timeInterval: 3.0, target: self, selector: #selector(self.autoScroll), userInfo: nil, repeats: true)
    
    
    @objc func autoScroll() 
        print("x is", self.x, self.allBanners.count)
        if self.x < self.allBanners.count 
            self.x = self.x + 1
         else 
            self.x = 1
        
//        DispatchQueue.main.async 
        self.resuableView.featuredCollectionView?.scrollToItem(at: IndexPath(item: self.x, section: 0), at: .centeredHorizontally, animated: true)
            self.resuableView.featuredCollectionView?.setNeedsLayout()
//        
    
    
    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) 
        let xPosition = scrollView.contentOffset.x
        let w = scrollView.bounds.size.width
        self.x = Int(ceil(xPosition/w))
        print("dec", self.x)
    

如果我打印 x 的值,它会正确打印。也就是说,每 3 秒后,它增加 1,但是,该 scrollToItem 不起作用。 另外,如果我禁用分页,那么它可以工作,但是,我确实需要分页。 即使我手动将集合视图滚动到任何项目,只要 @objc func autoScroll() 函数被调用,它也只会将其恢复到第一个项目。

有人知道为什么会这样吗? 附言我已经尝试过不使用异步、使用异步、不使用或使用 setNeedsLayout。 我有 Xcode 版本 12.0.1 (12A7300)

【问题讨论】:

我认为需要将异步调度到主队列,您可以尝试删除 setNeedsLayout 吗? 就像我说的,我已经尝试了所有这些可能的方法。事实上,原来的没有 setNeedsLayout() 或 async self.allBanners.count的值是多少? 那个数是 6 你有这行:self.resuableView.featuredCollectionView?.scrollToItem ... 而? 表示featuredCollectionView 可能无效。你有没有通过调试来确保它是?您能否发布额外的代码 - 足以重现该问题? 【参考方案1】:

我注意到了一些怪癖,尤其是 iOS 14...

如果一个单元格破坏了自动布局,scrollToItemAt 会惨遭失败,以及手动滚动。

这是一个完整的例子:


简单的细胞类

class MyTestCell: UICollectionViewCell 
    var cView: UIView = UIView()
    var theLabel: UILabel = UILabel()
    
    override init(frame: CGRect) 
        super.init(frame: frame)
        commonInit()
    
    required init?(coder: NSCoder) 
        super.init(coder: coder)
        commonInit()
    
    func commonInit() -> Void 

        // stylize the cell a little with a bordered-rounded-corner view inset by 8-pts
        cView.backgroundColor = .cyan
        cView.layer.borderWidth = 1
        cView.layer.borderColor = UIColor.blue.cgColor
        cView.layer.cornerRadius = 8

        cView.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(cView)
        
        theLabel.translatesAutoresizingMaskIntoConstraints = false
        cView.addSubview(theLabel)
        
        let g = contentView.layoutMarginsGuide
        
        NSLayoutConstraint.activate([
            
            cView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
            cView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
            cView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
            cView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),

            theLabel.topAnchor.constraint(equalTo: cView.topAnchor, constant: 8.0),
            theLabel.leadingAnchor.constraint(equalTo: cView.leadingAnchor, constant: 8.0),
            theLabel.trailingAnchor.constraint(equalTo: cView.trailingAnchor, constant: -8.0),
            theLabel.bottomAnchor.constraint(equalTo: cView.bottomAnchor, constant: -8.0),
            
        ])
        
        theLabel.textAlignment = .center
        theLabel.numberOfLines = 0
    


视图控制器类

class AutoScrollViewController: UIViewController 
    
    var collectionView: UICollectionView!

    var myData: [String] = []
    
    var x: Int = 0

    override func viewDidLoad() 
        super.viewDidLoad()
        
        // create 30 strings
        myData = (0..<30).map  "Cell \($0)" 

        // couple cells with longer text for variety
        myData[3] = "More text in cell 3"
        myData[7] = "The text in cell 7 is long enough to need to wrap onto multiple lines."

        // create a flow layout
        let f = setupFlowLayout()
        
        // create the collection view
        collectionView = UICollectionView(frame: .zero, collectionViewLayout: f)
        
        // we want to use paging
        collectionView.isPagingEnabled = true
        
        // register the cell
        collectionView.register(MyTestCell.self, forCellWithReuseIdentifier: "cell")
        
        // data source and delgate
        collectionView.dataSource = self
        collectionView.delegate = self
        
        // add a "Tap instruction" label
        let instLabel = UILabel()
        instLabel.numberOfLines = 0
        instLabel.textAlignment = .center
        instLabel.text = "Cells will auto-advance\nevery 3 seconds.\n\nYou can also Tap here\nto jump to Cell 23"
        instLabel.translatesAutoresizingMaskIntoConstraints = false

        // add UI elements
        view.addSubview(collectionView)
        view.addSubview(instLabel)

        collectionView.translatesAutoresizingMaskIntoConstraints = false
        instLabel.translatesAutoresizingMaskIntoConstraints = false
            
        // respect safe area
        let g = view.safeAreaLayoutGuide
        
        NSLayoutConstraint.activate([
            
            // constrain collection view Top / Leading / Trailing + 40-pts
            collectionView.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
            collectionView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
            collectionView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),
            // collection view Height: 160
            collectionView.heightAnchor.constraint(equalToConstant: 160.0),
            
            // constrain instruction label below and centered to the collection view
            instLabel.topAnchor.constraint(equalTo: collectionView.bottomAnchor, constant: 40.0),
            instLabel.centerXAnchor.constraint(equalTo: collectionView.centerXAnchor),
            instLabel.widthAnchor.constraint(equalTo: collectionView.widthAnchor),
            
        ])
        
        // for testing, add a tap recognizer to the view
        let t = UITapGestureRecognizer(target: self, action: #selector(self.tapScroll))
        view.addGestureRecognizer(t)

    
    
    override func viewDidAppear(_ animated: Bool) 
        super.viewDidAppear(animated)
        // set collection view item size here
        if let f = collectionView.collectionViewLayout as? UICollectionViewFlowLayout 
            f.itemSize = CGSize(width: collectionView.frame.width, height: collectionView.frame.height)
        
        setTimer()
    

    func setTimer() 
        let _ = Timer.scheduledTimer(timeInterval: 3.0, target: self, selector: #selector(self.autoScroll), userInfo: nil, repeats: true)
    
    
    @objc func tapScroll() 
        self.x = 23
        collectionView.scrollToItem(at: IndexPath(item: self.x, section: 0), at: .left, animated: true)
    
    
    @objc func autoScroll() 
        print("x is", self.x, "Cell Count is", self.myData.count)
        self.x = self.x + 1
        if self.x >= self.myData.count 
            self.x = 0
        
        collectionView.scrollToItem(at: IndexPath(item: self.x, section: 0), at: .left, animated: true)
    



带有数据源和委托实现的扩展

extension AutoScrollViewController: UICollectionViewDataSource, UICollectionViewDelegate 
    
    private func setupFlowLayout() -> UICollectionViewFlowLayout 
        let flowLayout = UICollectionViewFlowLayout()
        flowLayout.scrollDirection = .horizontal
        flowLayout.sectionInset = .zero
        flowLayout.minimumLineSpacing = 0
        flowLayout.minimumInteritemSpacing = 1
        return flowLayout
    
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int 
        return myData.count
    
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell 
        let c = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! MyTestCell
        // alternate cell background color so we can see it
        c.contentView.backgroundColor = indexPath.item % 2 == 0 ? .orange : .yellow
        c.theLabel.text = myData[indexPath.item]
        return c
    

    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) 
        let xPosition = scrollView.contentOffset.x
        let w = scrollView.bounds.size.width
        self.x = Int(ceil(xPosition/w))
        print("dec", xPosition, w, self.x)
    



没有@IBOutlet@IBAction 连接,因此只需为AutoScrollViewController 的自定义类分配一个默认的UIViewController(来自上述代码)。

【讨论】:

以上是关于UICollectionView 不自动滚动的主要内容,如果未能解决你的问题,请参考以下文章

在 UICollectionView/UITableView 中为滚动视图的偏移设置动画会导致单元格过早消失

UICollectionView 不自动滚动

如何让 UICollectionView 在不离开屏幕的情况下使用自动布局填充其包含滚动视图的宽度?

如何使用 Timer 在 UITableView 内自动滚动 UICollectionView?

当视图显示为幻灯片时,自动滚动 UICollectionView 元素。

UICollectionView 水平分页仅在第一页上每行显示 3 个单元格