我有UICollectionView,它使用UIPanGestureRecognizer基于UIViewPropertyAnimator更改UICollectionViewLayout(向下然后向上)。动画期间会出现鬼单元。 UICollectionView在动画结束时是正确的,但动画中间的幻影单元是不正确的。
即使使用UIView动画也可以看到问题,所以与UIViewPropertyAnimator无关。
这是显示问题的完整示例项目的link。
使用Xcode 10.1 / Swift 4.2
运行示例应用程序,然后向下和平移。
这是使用UIViewPropertyAnimator平移时的外观。
以下是动画代码:
class TestButtonsFactory: NSObject {
private var selectedPath: NSIndexPath?
/// The collectionView used to represent the buttons in the advanced portrait feature.
let buttonsView: UICollectionView
/// A computed var that returns the UICollectionView's layout being used to display the advanced features.
internal var theLayout: UICollectionViewFlowLayout {
return (buttonsView.collectionViewLayout as? UICollectionViewFlowLayout)!
}
var viewFrame: CGRect {
var baseFrame = UIScreen.main.bounds
if shortPortraitIsActive {
let height = baseFrame.height * 0.3
baseFrame = CGRect(x: baseFrame.origin.x, y: baseFrame.origin.y + height,
width: baseFrame.width, height: baseFrame.height-height)
}
return baseFrame
}
var itemSize: CGSize {
let columns = CGFloat(3) // CGFloat(viewModel.columnsInLayout)
let rows = CGFloat(7) // CGFloat(viewModel.rowsInLayout)
let spacing = CGFloat(10 * 2.0)
let frame = viewFrame
return CGSize(width: (frame.width - (columns + 1) * spacing) / columns,
height: (frame.height - (rows + 1) * spacing) / rows )
}
private func cellItem(for tag: Int) -> TestButtonCell? {
return buttonsView.visibleCells.filter{ $0.theButtonCell.button.tag == tag}.first?.theButtonCell
}
override init() {
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.itemSize = CGSize(width: 70, height: 20)
buttonsView = UICollectionView(frame: .zero, collectionViewLayout: layout)
super.init()
buttonsView.dataSource = self
buttonsView.allowsMultipleSelection = false
buttonsView.allowsSelection = true
buttonsView.bounces = false
buttonsView.clearsContextBeforeDrawing = true
buttonsView.register(TestButtonCell.self, forCellWithReuseIdentifier: "CellID")
}
internal func prepareLayout() {
let spacing = CGFloat(10)
buttonsView.frame = viewFrame
theLayout.itemSize = itemSize
theLayout.minimumInteritemSpacing = spacing
theLayout.minimumLineSpacing = spacing
// Needed to correct problem where sometimes only one row (instead of two) shows in iOS 10.x
buttonsView.contentInset = UIEdgeInsets(top: spacing, left: spacing, bottom: spacing, right: spacing)
}
func updateViewForTheme(_ animated: Bool = false) -> Void {
self.buttonsView.backgroundColor = UIColor.clear // colorTheme.backgroundColor
// buttonsView.performBatchUpdates({
prepareLayout()
dPrint("* * * updateViewForTheme * * * ")
// }, completion: { (result: Bool) in
// self.buttonsView.reloadVisibleCells()
// }
// )
}
}
这是动画师如何启动和受到控制的方式。
/// Handler for gesture that enables the Advanced Portrait feature. The gesture uses a property animator to animate enabling or disabling.
/// the feature.
///
/// - Parameter gesture: The pan gesture.
@objc func panForInteractiveTransition(_ gesture: UIPanGestureRecognizer) -> Void {
let offset = gesture.translation(in: mainButtons.buttonsView)// mainVC.clearButton)
let velocity = gesture.velocity(in: mainButtons.buttonsView)//mainVC.clearButton)
switch gesture.state {
case .began:
startAnimatorIfNeeded(offset: offset, velocity: velocity, isActive: shortPortraitIsActive){
shortPortraitIsActive.toggle()
shortPortraitInInteractiveAnimation = true
}
case .changed:
startAnimatorIfNeeded(offset: offset, velocity: velocity, isActive: shortPortraitIsActive){
shortPortraitIsActive.toggle()
shortPortraitInInteractiveAnimation = true
}
guard featuresAnimator.state == .active else { break }
// Protect against wrap-around & edge cases
let offset = (shortPortraitIsActive && offset.y < 0.0) || (!shortPortraitIsActive && offset.y > 0.0) ? 0.001 : abs(offset.y)
featuresAnimator.fractionComplete = offset / (mainButtons.buttonsView.frame.height/4.0)
case .ended:
guard featuresAnimator.state == .active else { break }
// If interactive progress was < 1/3 then assume that user did not want the change; stop the running animator and
// start another animator going back to the previous state.
if featuresAnimator.fractionComplete < (1.0/3.0) {
featuresAnimator.stopAnimation(true)
shortPortraitIsActive.toggle()
UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 0.3, delay: 0, options: .curveEaseInOut, animations: {
self.mainButtons.updateViewForTheme(true)
})
} else {
featuresAnimator.continueAnimation(withTimingParameters: nil, durationFactor: 0)
}
shortPortraitInInteractiveAnimation = false
mainButtons.updateViewForTheme(true)
default:
()
}
}
/**
This method starts the property animator to expose the advanced features in portrait mode if it's not already started.
The animator only starts if will generate a valid animation; meaning a pan down only makes sense if the feature is hidden and a
pan up only makes sense if the feature is exposed.
This method modifies the private animator property: *advancedFeaturesAnimator*.
- parameter offset: The current translation of the pan gesture in the view attached to the gesture.
- parameter velocity: The current velocity of the pan gesture in the view attached to the gesture. Presently not used.
- returns: The resulting state of the animator.
*/
@discardableResult private func startAnimatorIfNeeded( offset:CGPoint, velocity:CGPoint, isActive: Bool, closure:@escaping ()->() ) -> UIViewAnimatingState {
guard featuresAnimator.state == .inactive else { return featuresAnimator.state }
if offset.y > 0 && !isActive || offset.y < 0 && isActive {
dPrint("* * * staring animation * * * ")
self.mainButtons.theLayout.invalidateLayout()
featuresAnimator = UIViewPropertyAnimator(duration: 1.0, curve: .easeInOut, animations: {
closure()
// self.mainButtons.theLayout.invalidateLayout()
self.mainButtons.updateViewForTheme()
})
featuresAnimator.startAnimation()
featuresAnimator.pauseAnimation()
}
dPrint("startAnimatorIfNeeded returning: \(featuresAnimator.state.rawValue)")
return featuresAnimator.state
}