如何修复在 UICollectionView 中使用 setContentOffset 时的动画错误

问题描述 投票:0回答:1

在我当前的项目中,我有一个包含 2 个单元格的 collectionView。第一个单元格的宽度和高度等于 iPhone 屏幕的高度和宽度。在底部,我有一个带有 2 个图标的自定义导航栏。当我使用:

let point = CGPoint(x: screenWidth, y: 0)
collectionView.setContentOffset(point, animated: true)

动画效果很好,但对我来说有点太慢了。但是当我使用时:

 UIView.animate(withDuration: 0.2, animations: {
     self.collectionView.setContentOffset(point, animated: false)
 })

然后第一个单元格不会像第一个动画那样移出屏幕(向左),而是立即消失,然后第二个单元格从右侧移入屏幕。

有人知道为什么第一个单元格消失了吗?

ios swift animation uicollectionview
1个回答
0
投票

如果你只有 两个单元格,你可能会更好(出于几个原因)让它们在滚动视图中查看。

但是,如果您想坚持使用双单元集合视图...

当您告诉 UIKit 为

.contentOffset
变化设置动画时,UIKit 会找出在动画结束时需要显示什么

因为你正在滚动一个单元格完全超出视野,UIKit 决定它不需要在动画期间管理那个单元格。

一种解决方案是将

.contentOffset.x
设置为一点比需要的少,然后在动画的完成块中“完成”定位。

所以...

如果我们要从单元格

0
到单元格
1
(从右向左滑动):

  • 动画到
    x: screenWidth - 1.0
  • 设置
    .contentOffset.x = screenWidth
    完成

如果我们要从单元格

1
到单元格
0
(从左向右滑动):

  • 动画到
    x: 1.0
  • 设置
    .contentOffset.x = 0.0
    完成

示例代码:

func animateToFirstCell() {
    var point: CGPoint = .init(x: 1.0, y: 0.0)
    UIView.animate(withDuration: 0.2, animations: {
        self.collectionView.setContentOffset(point, animated: false)
    }, completion: { _ in
        point.x = 0.0
        self.collectionView.setContentOffset(point, animated: false)
    })
}
func animateToSecondCell() {
    var point: CGPoint = .init(x: screenWidth - 1.0, y: 0.0)
    UIView.animate(withDuration: 0.2, animations: {
        self.collectionView.setContentOffset(point, animated: false)
    }, completion: { _ in
        point.x = self.screenWidth
        self.collectionView.setContentOffset(point, animated: false)
    })
}

编辑

UICollectionView
的一些好处:

  • 流(或自定义)布局
  • 内存管理 - 允许大量的单元格
  • 单元格/项目选择

如果你只有两个“全视图单元格”,你可以在它们之间切换,所以你没有使用集合视图的任何其他好处,你可能会发现将两个视图放在滚动视图中要容易得多。

这种方法的优点之一是您的视图(“单元格”)不会在您每次来回滚动时被删除/添加。

这是一个简单的例子......

我们将使用

UICollectionViewCell
子类代替
UIView

class ViewInsteadOfCell: UIView {
    
    let theLabel: UILabel = {
        let v = UILabel()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
        v.textAlignment = .center
        v.textColor = .black
        return v
    }()
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    func commonInit() -> Void {
        addSubview(theLabel)
        NSLayoutConstraint.activate([
            theLabel.centerXAnchor.constraint(equalTo: centerXAnchor),
            theLabel.centerYAnchor.constraint(equalTo: centerYAnchor),
            theLabel.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 0.75),
            theLabel.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.75),
        ])
    }
    
}

以及一个示例视图控制器,展示了如何实现它:

class ScrollViewExampleVC: UIViewController {
    
    var scrollView: UIScrollView = UIScrollView()
    
    // let's define this here, so we can "play with the speed"
    //  to make it easier to see the difference (e.g. the "cells" don't disappear)
    let animDuration: Double = 0.2
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // instead of a collection view with cells,
        //  let's use a stack view with two "cell" views
        let stackView = UIStackView()
        
        stackView.distribution = .fillEqually
        
        // these are defaults, but just for clarity
        stackView.axis = .horizontal
        stackView.spacing = 0
        
        let cellA = ViewInsteadOfCell()
        cellA.backgroundColor = .systemRed
        cellA.theLabel.text = "First"
        stackView.addArrangedSubview(cellA)
        
        let cellB = ViewInsteadOfCell()
        cellB.backgroundColor = .systemGreen
        cellB.theLabel.text = "Second"
        stackView.addArrangedSubview(cellB)

        // two buttons for the bottom to switch
        //  between "Left" and "Right"
        var cfg = UIButton.Configuration.filled()
        
        cfg.title = "First"
        let btnA = UIButton(configuration: cfg)
        btnA.addAction (
            UIAction { _ in
                self.animateToFirstCell()
            }, for: .touchUpInside
        )
        
        cfg.title = "Second"
        let btnB = UIButton(configuration: cfg)
        btnB.addAction (
            UIAction { _ in
                self.animateToSecondCell()
            }, for: .touchUpInside
        )
        
        // don't let buttons expand vertically
        btnA.setContentHuggingPriority(.required, for: .vertical)
        btnB.setContentHuggingPriority(.required, for: .vertical)

        stackView.translatesAutoresizingMaskIntoConstraints = false
        scrollView.addSubview(stackView)
        
        scrollView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(scrollView)
        
        btnA.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(btnA)

        btnB.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(btnB)
        
        let g = view.safeAreaLayoutGuide
        let cg = scrollView.contentLayoutGuide
        let fg = scrollView.frameLayoutGuide
        
        NSLayoutConstraint.activate([

            // buttons at the bottom
            btnA.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -8.0),
            btnA.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
            
            btnB.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -8.0),
            btnB.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
            
            btnB.leadingAnchor.constraint(equalTo: btnA.trailingAnchor, constant: 8.0),
            btnB.widthAnchor.constraint(equalTo: btnA.widthAnchor),
            
            // scroll view top/leading/trailing to view safe area
            //  bottom to top of buttons
            scrollView.topAnchor.constraint(equalTo: g.topAnchor),
            scrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
            scrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
            scrollView.bottomAnchor.constraint(equalTo: btnA.topAnchor, constant: -8.0),

            // stack view constrained all four sides to scroll view's Content Layout Guide
            stackView.topAnchor.constraint(equalTo: cg.topAnchor),
            stackView.leadingAnchor.constraint(equalTo: cg.leadingAnchor),
            stackView.trailingAnchor.constraint(equalTo: cg.trailingAnchor),
            stackView.bottomAnchor.constraint(equalTo: cg.bottomAnchor),
            
            // stack view height equal to scroll view Frame Layout Guide height
            stackView.heightAnchor.constraint(equalTo: fg.heightAnchor),
            // stack view width equal to scroll view Frame Layout Guide width * 2
            stackView.widthAnchor.constraint(equalTo: fg.widthAnchor, multiplier: 2.0),
            
        ])
    }
    
    func animateToFirstCell() {
        UIView.animate(withDuration: animDuration, animations: {
            self.scrollView.setContentOffset(.init(x: 0.0, y: 0.0), animated: false)
        })
    }
    func animateToSecondCell() {
        UIView.animate(withDuration: animDuration, animations: {
            self.scrollView.setContentOffset(.init(x: self.scrollView.frame.width, y: 0.0), animated: false)
        })
    }
    
}
© www.soinside.com 2019 - 2024. All rights reserved.