动画期间更改collectionViewLayout的鬼像元

时间:2019-02-13 01:23:16

标签: animation uicollectionviewlayout

我有UICollectionView,它使用UIPanGestureRecognizer基于UIViewPropertyAnimator更改UICollectionViewLayout(向下然后向上)。动画期间会出现鬼单元。 UICollectionView在动画结束时是正确的,但动画中间的幻影单元是不正确的。

即使使用UIView动画也可以看到问题,所以与UIViewPropertyAnimator无关。

这是显示问题的完整示例项目的link

使用Xcode 10.1 / Swift 4.2

运行示例应用程序,然后向下和平移。

这是它的开始方式: enter image description here

这是使用UIViewPropertyAnimator平移时的外观。 enter image description here

这是结束的方式(正确)。 enter image description here

以下是动画代码:

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
  }

0 个答案:

没有答案