在 collectionviewcell 中动画化视图
Animating a view inside a collectionviewcell
我有点卡住了 progressView 的动画(使用这个子class:Linear Progress View)。
只是一些背景知识,所以下面发布的代码很有意义。我创建了具有一些基本功能的 UICollectionViewCell
的子 class。这样做的主要原因是减少样板代码。在我的集合视图中,我将单元格设置为我的 subclass 并将模型对象传递给它。所有数据都正确显示,但进度视图没有动画。我在集合视图上尝试过 viewWillDisplay
之类的东西,但无济于事。
如有任何建议,我们将不胜感激。 (下面的代码)
CollectionViewCell class:
import UIKit
import ChameleonFramework
import Material
class MacrocycleCell: Cell {
var macrocycle:Macrocycle? = nil {
didSet{
if let macro = macrocycle, let start = macrocycle?.start, let completion = macrocycle?.completion {
title.text = macro.title
let percentage = Date().calculatePercentageComplete(startDate: start, completionDate: completion)
progress.setProgress(percentage, animated: true)
let difference = Date.calculateDifferenceInMonthsAndDaysBetween(start: start, end: completion)
if let monthDiff = difference.month, let dayDiff = difference.day {
if monthDiff > 0 {
detail.text = monthDiff > 1 ? "\(monthDiff) months left" : "\(monthDiff) month left"
}else{
detail.text = dayDiff > 1 ? "\(dayDiff) days left" : "\(dayDiff) day left"
}
}
}
}
}
let dropView:View = {
let view = View()
view.backgroundColor = .white
view.translatesAutoresizingMaskIntoConstraints = false
view.depthPreset = DepthPreset.depth2
return view
}()
let title:UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.textAlignment = .center
label.font = UIFont.boldSystemFont(ofSize: 18)
label.textColor = UIColor.flatBlack()
return label
}()
let progress:LinearProgressView = {
let progress = LinearProgressView()
progress.isCornersRounded = true
progress.barColor = UIColor.flatWhiteColorDark()
progress.trackColor = UIColor.flatGreen()
progress.barInset = 0
progress.minimumValue = 0
progress.maximumValue = 1
progress.animationDuration = 1
progress.translatesAutoresizingMaskIntoConstraints = false
return progress
}()
let detail:UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.textAlignment = .center
label.font = UIFont.systemFont(ofSize: 14, weight: UIFont.Weight.light)
label.textColor = UIColor.flatGray()
return label
}()
override func drawView() {
addSubview(dropView)
dropView.addSubview(title)
dropView.addSubview(progress)
dropView.addSubview(detail)
dropView.topAnchor.constraint(equalTo: topAnchor).isActive = true
dropView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
dropView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
dropView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -2).isActive = true
progress.centerXAnchor.constraint(equalTo: dropView.centerXAnchor).isActive = true
progress.centerYAnchor.constraint(equalTo: dropView.centerYAnchor).isActive = true
progress.heightAnchor.constraint(equalToConstant: 10).isActive = true
progress.widthAnchor.constraint(equalTo: dropView.widthAnchor, multiplier: 0.8).isActive = true
title.topAnchor.constraint(equalTo: dropView.topAnchor).isActive = true
title.leftAnchor.constraint(equalTo: dropView.safeAreaLayoutGuide.leftAnchor).isActive = true
title.rightAnchor.constraint(equalTo: dropView.safeAreaLayoutGuide.rightAnchor).isActive = true
title.bottomAnchor.constraint(equalTo: progress.topAnchor, constant: 6).isActive = true
detail.topAnchor.constraint(equalTo: progress.bottomAnchor, constant: 14).isActive = true
detail.leftAnchor.constraint(equalTo: dropView.leftAnchor).isActive = true
detail.rightAnchor.constraint(equalTo: dropView.rightAnchor).isActive = true
detail.heightAnchor.constraint(equalToConstant: 14).isActive = true
}
}
CollectionViewControllerClass:
import UIKit
private let reuseIdentifier = "Cell"
class MacrocycleController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
var athlete:Athlete? = nil {
didSet{
self.title = athlete?.name
}
}
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.backgroundColor = .flatWhiteColorDark()
self.collectionView!.register(MacrocycleCell.self, forCellWithReuseIdentifier: reuseIdentifier)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: UICollectionViewDataSource
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width, height: 90)
}
override func numberOfSections(in collectionView: UICollectionView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of items
return athlete?.macrocycles?.count ?? 0
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! MacrocycleCell
cell.macrocycle = athlete?.macrocycles?[indexPath.item]
return cell
}
}
我确定这很简单,我忘记实施了。
谢谢你们这些美丽的人!
您没有看到任何动画,因为没有要设置动画的可见变化。具体来说,您在显示单元格之前从 didSet 调用 setProgress,然后您再也不会调用它。
要查看动画,您必须允许视图以某个初始值 (0%) 显示,然后再次调用 setProgress,将更改动画化为真值。
从 UI 的角度来看,您尝试做的事情不是一个好主意 -- 原则上,进度条应该仅在某些实际有意义的进展(用户操作、下载等)发生时才移动。因此,我对你的问题的回答是在这种情况下使用没有动画的进度条。
也就是说,在某些合法情况下,您可能希望更新单元格中的进度条而不必重新加载单元格。
例如,如果您有一些后台任务 运行 定期报告与每个单元格相关的进度。有大量的答案展示了进行这种更新的不同方法。
此外,已建议使用各种 hack 来在显示单元格后立即触发更新。如果您想查看其中一些想法,请搜索不存在的 "DidDisplayCell" 委托方法的问题。您绝对可以在单元格显示之后制作动画,但我想让您远离将动画用于人为目的的诱惑。
这里有一些代码片段来说明如何使用 WillDisplay 中的 asyncAfter 执行此操作(我用 tableview 对此进行了测试):
// This would be in your cell class
var progress: Float = 0.0
@IBOutlet var progressBar: LinearProgressView!
func animateIfNeeded() {
if progressBar.progress != self.progress {
progressBar.setProgress(progress, animated: true)
}
}
// This would be in your delegate (I tested with tableview)
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
let animateCell = cell as! Cell
animateCell.animateIfNeeded()
}
}
显然,在 didSet 中,您只需将计算值保存到一个实例变量中(进度:Float)并调用 progressBar.setProgress(0,animated: false)。
这是另一个答案,它将所有逻辑保留在单元格 class 中并且不使用集合控制器的 WillDisplayCell 委托方法。
var progress: Float = 0.0
@IBOutlet var progressBar: LinearProgressView!
var drawnAtLeastOnce = false
func animateIfNeeded() {
if progressBar.progress != self.progress {
progressBar.setProgress(progress, animated: true)
}
}
override func layoutSubviews() {
super.layoutSubviews()
if drawnAtLeastOnce {
animateIfNeeded()
}
}
override func draw(_ rect: CGRect) {
super.draw(rect)
if !drawnAtLeastOnce {
drawnAtLeastOnce = true
setNeedsLayout()
}
}
这个答案的关键是检测第一个绘图通道的结束并在该时间之前抑制动画。
这对于第一个屏幕单元格非常有效。滚动到视图中的单元格可能是预先绘制的,因此您不会在这些单元格上看到动画。 (至少这是我在使用 tableview 进行测试时观察到的)。
我有点卡住了 progressView 的动画(使用这个子class:Linear Progress View)。
只是一些背景知识,所以下面发布的代码很有意义。我创建了具有一些基本功能的 UICollectionViewCell
的子 class。这样做的主要原因是减少样板代码。在我的集合视图中,我将单元格设置为我的 subclass 并将模型对象传递给它。所有数据都正确显示,但进度视图没有动画。我在集合视图上尝试过 viewWillDisplay
之类的东西,但无济于事。
如有任何建议,我们将不胜感激。 (下面的代码)
CollectionViewCell class:
import UIKit
import ChameleonFramework
import Material
class MacrocycleCell: Cell {
var macrocycle:Macrocycle? = nil {
didSet{
if let macro = macrocycle, let start = macrocycle?.start, let completion = macrocycle?.completion {
title.text = macro.title
let percentage = Date().calculatePercentageComplete(startDate: start, completionDate: completion)
progress.setProgress(percentage, animated: true)
let difference = Date.calculateDifferenceInMonthsAndDaysBetween(start: start, end: completion)
if let monthDiff = difference.month, let dayDiff = difference.day {
if monthDiff > 0 {
detail.text = monthDiff > 1 ? "\(monthDiff) months left" : "\(monthDiff) month left"
}else{
detail.text = dayDiff > 1 ? "\(dayDiff) days left" : "\(dayDiff) day left"
}
}
}
}
}
let dropView:View = {
let view = View()
view.backgroundColor = .white
view.translatesAutoresizingMaskIntoConstraints = false
view.depthPreset = DepthPreset.depth2
return view
}()
let title:UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.textAlignment = .center
label.font = UIFont.boldSystemFont(ofSize: 18)
label.textColor = UIColor.flatBlack()
return label
}()
let progress:LinearProgressView = {
let progress = LinearProgressView()
progress.isCornersRounded = true
progress.barColor = UIColor.flatWhiteColorDark()
progress.trackColor = UIColor.flatGreen()
progress.barInset = 0
progress.minimumValue = 0
progress.maximumValue = 1
progress.animationDuration = 1
progress.translatesAutoresizingMaskIntoConstraints = false
return progress
}()
let detail:UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.textAlignment = .center
label.font = UIFont.systemFont(ofSize: 14, weight: UIFont.Weight.light)
label.textColor = UIColor.flatGray()
return label
}()
override func drawView() {
addSubview(dropView)
dropView.addSubview(title)
dropView.addSubview(progress)
dropView.addSubview(detail)
dropView.topAnchor.constraint(equalTo: topAnchor).isActive = true
dropView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
dropView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
dropView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -2).isActive = true
progress.centerXAnchor.constraint(equalTo: dropView.centerXAnchor).isActive = true
progress.centerYAnchor.constraint(equalTo: dropView.centerYAnchor).isActive = true
progress.heightAnchor.constraint(equalToConstant: 10).isActive = true
progress.widthAnchor.constraint(equalTo: dropView.widthAnchor, multiplier: 0.8).isActive = true
title.topAnchor.constraint(equalTo: dropView.topAnchor).isActive = true
title.leftAnchor.constraint(equalTo: dropView.safeAreaLayoutGuide.leftAnchor).isActive = true
title.rightAnchor.constraint(equalTo: dropView.safeAreaLayoutGuide.rightAnchor).isActive = true
title.bottomAnchor.constraint(equalTo: progress.topAnchor, constant: 6).isActive = true
detail.topAnchor.constraint(equalTo: progress.bottomAnchor, constant: 14).isActive = true
detail.leftAnchor.constraint(equalTo: dropView.leftAnchor).isActive = true
detail.rightAnchor.constraint(equalTo: dropView.rightAnchor).isActive = true
detail.heightAnchor.constraint(equalToConstant: 14).isActive = true
}
}
CollectionViewControllerClass:
import UIKit
private let reuseIdentifier = "Cell"
class MacrocycleController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
var athlete:Athlete? = nil {
didSet{
self.title = athlete?.name
}
}
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.backgroundColor = .flatWhiteColorDark()
self.collectionView!.register(MacrocycleCell.self, forCellWithReuseIdentifier: reuseIdentifier)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: UICollectionViewDataSource
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width, height: 90)
}
override func numberOfSections(in collectionView: UICollectionView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of items
return athlete?.macrocycles?.count ?? 0
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! MacrocycleCell
cell.macrocycle = athlete?.macrocycles?[indexPath.item]
return cell
}
}
我确定这很简单,我忘记实施了。
谢谢你们这些美丽的人!
您没有看到任何动画,因为没有要设置动画的可见变化。具体来说,您在显示单元格之前从 didSet 调用 setProgress,然后您再也不会调用它。
要查看动画,您必须允许视图以某个初始值 (0%) 显示,然后再次调用 setProgress,将更改动画化为真值。
从 UI 的角度来看,您尝试做的事情不是一个好主意 -- 原则上,进度条应该仅在某些实际有意义的进展(用户操作、下载等)发生时才移动。因此,我对你的问题的回答是在这种情况下使用没有动画的进度条。
也就是说,在某些合法情况下,您可能希望更新单元格中的进度条而不必重新加载单元格。 例如,如果您有一些后台任务 运行 定期报告与每个单元格相关的进度。有大量的答案展示了进行这种更新的不同方法。
此外,已建议使用各种 hack 来在显示单元格后立即触发更新。如果您想查看其中一些想法,请搜索不存在的 "DidDisplayCell" 委托方法的问题。您绝对可以在单元格显示之后制作动画,但我想让您远离将动画用于人为目的的诱惑。
这里有一些代码片段来说明如何使用 WillDisplay 中的 asyncAfter 执行此操作(我用 tableview 对此进行了测试):
// This would be in your cell class
var progress: Float = 0.0
@IBOutlet var progressBar: LinearProgressView!
func animateIfNeeded() {
if progressBar.progress != self.progress {
progressBar.setProgress(progress, animated: true)
}
}
// This would be in your delegate (I tested with tableview)
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
let animateCell = cell as! Cell
animateCell.animateIfNeeded()
}
}
显然,在 didSet 中,您只需将计算值保存到一个实例变量中(进度:Float)并调用 progressBar.setProgress(0,animated: false)。
这是另一个答案,它将所有逻辑保留在单元格 class 中并且不使用集合控制器的 WillDisplayCell 委托方法。
var progress: Float = 0.0
@IBOutlet var progressBar: LinearProgressView!
var drawnAtLeastOnce = false
func animateIfNeeded() {
if progressBar.progress != self.progress {
progressBar.setProgress(progress, animated: true)
}
}
override func layoutSubviews() {
super.layoutSubviews()
if drawnAtLeastOnce {
animateIfNeeded()
}
}
override func draw(_ rect: CGRect) {
super.draw(rect)
if !drawnAtLeastOnce {
drawnAtLeastOnce = true
setNeedsLayout()
}
}
这个答案的关键是检测第一个绘图通道的结束并在该时间之前抑制动画。
这对于第一个屏幕单元格非常有效。滚动到视图中的单元格可能是预先绘制的,因此您不会在这些单元格上看到动画。 (至少这是我在使用 tableview 进行测试时观察到的)。