Призрачные ячейки во время анимации, которые изменяют collectionViewLayout - PullRequest
0 голосов
/ 13 февраля 2019

У меня есть UICollectionView, который изменяет UICollectionViewLayout на основе UIViewPropertyAnimator с использованием UIPanGestureRecognizer (панорамирование вниз, а затем вверх).Призрачные клетки появляются во время анимации.UICollectionView корректен в конце анимации, но призрачные ячейки в середине анимации неверны.

Проблема может быть замечена даже с анимацией UIView, поэтому она не имеет ничего общего с UIViewPropertyAnimator.

Вот ссылка на полный пример проекта, в котором показана проблема.

Использование 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
  }
...