Неожиданный глюк анимации, связанный с прокруткой вверх? - PullRequest
0 голосов
/ 28 апреля 2018

Прежде всего, извините, что это не очень общий вопрос (он довольно специфичен), но я стучу головой об стену и не могу ничего найти (очевидно).

Итак, я построил эту библиотеку, чтобы смахивать стопку карт на основе UICollectionView, и все работает отлично, за исключением , когда я нажимаю на строку состояния, чтобы «прокрутить вверх», а затем пытаюсь чтобы убрать карту, анимация как-то делает что-то странное Я не могу ни объяснить, ни понять. Еще одна вещь, которая случается в нижней части анимации, также не работает

Вы можете найти исходный код на Github здесь

Вот проблема Github с дополнительной информацией: https://github.com/JoniVR/VerticalCardSwiper/issues/4

Любая помощь по этому вопросу будет принята с благодарностью! Хорошего дня:)

вот демо:

demo

Вот некоторые из наиболее важных фрагментов кода (я думаю):

CardCell:

/// The CardCell that the user can swipe away. Based on `UICollectionViewCell`.
class CardCell: UICollectionViewCell {

    weak var delegate: CardCellSwipeDelegate?

    @IBOutlet weak var positionLbl: UILabel!

    override func layoutSubviews() {

        self.layer.cornerRadius = 12
        self.layer.shouldRasterize = true
        self.layer.rasterizationScale = UIScreen.main.scale

        // make sure anchorPoint is correct when laying out subviews.
        self.layer.anchorPoint = CGPoint(x: 0.5, y: 0.5)
        super.layoutSubviews()
    }

    /**
     We use this function to calculate and set a random backgroundcolor.
     */
    public func setRandomBackgroundColor(){

        let randomRed:CGFloat = CGFloat(arc4random()) / CGFloat(UInt32.max)
        let randomGreen:CGFloat = CGFloat(arc4random()) / CGFloat(UInt32.max)
        let randomBlue:CGFloat = CGFloat(arc4random()) / CGFloat(UInt32.max)

        self.backgroundColor = UIColor(red: randomRed, green: randomGreen, blue: randomBlue, alpha: 1.0)
    }

    /**
     This function animates the card. The animation consists of a rotation and translation.
     - parameter angle: The angle the card rotates while animating.
     - parameter horizontalTranslation: The horizontal translation the card animates in.
     */
    public func animateCard(angle: CGFloat, horizontalTranslation: CGFloat){

        var transform = CATransform3DIdentity
        transform = CATransform3DRotate(transform, angle, 0, 0, 1)
        transform = CATransform3DTranslate(transform, horizontalTranslation, 0, 1)
        layer.transform = transform
    }

    /**
     Resets the CardCell back to the center of the CollectionView.
    */
    public func resetToCenterPosition(){

        UIView.animate(withDuration: 0.2, animations: { [weak self] in
            self?.layer.transform = CATransform3DIdentity
        })
    }

    /**
     Called when the pan gesture is ended.
     Handles what happens when the user stops swiping a card.
     If a certain treshold of the screen is swiped, the `animateOffScreen` function is called,
     if the threshold is not reached, the card will be reset to the center by calling `resetToCenterPosition`.
     - parameter direction: The direction of the pan gesture.
     - parameter centerX: The center X point of the swipeAbleArea/collectionView.
     - parameter angle: The angle of the animation, depends on the direction of the swipe.
    */
    public func endedPanAnimation(withDirection direction: PanDirection, centerX: CGFloat, angle: CGFloat){

        let swipePercentageMargin = self.bounds.width * 0.4
        let cardCenter = self.convert(CGPoint(x: self.bounds.midX, y: self.bounds.midY), to: self.superview)

        if (cardCenter.x > centerX + swipePercentageMargin || cardCenter.x < centerX - swipePercentageMargin){
            animateOffScreen(angle: angle)
        } else {
            self.resetToCenterPosition()
        }
    }

    /**
     Animates to card off the screen and calls the `didSwipeAway` function from the `CardCellSwipeDelegate`.
     - parameter angle: The angle that the card will rotate in (depends on direction). Positive means the card is swiped to the right, a negative angle means the card is swiped to the left.
    */
    fileprivate func animateOffScreen(angle: CGFloat){

        var direction: CellSwipeDirection = .None

        var transform = CATransform3DIdentity
        transform = CATransform3DRotate(transform, angle, 0, 0, 1)

        // swipe left
        if angle < 0 {
            transform = CATransform3DTranslate(transform, -(self.frame.width * 2), 0, 1)
            direction = .Left
        }

        // swipe right
        if angle > 0 {
            transform = CATransform3DTranslate(transform, (self.frame.width * 2), 0, 1)
            direction = .Right
        }

        UIView.animate(withDuration: 0.2, animations: { [weak self] in

            self?.layer.transform = transform
        })
        delegate?.didSwipeAway(cell: self, swipeDirection: direction)
    }

    /**
     Prepares for reuse by resetting the anchorPoint back to the default value.
     This is necessary because in HomeVC we are manipulating the anchorPoint during dragging animation.
    */
    override func prepareForReuse() {
        super.prepareForReuse()
        // reset to default value (https://developer.apple.com/documentation/quartzcore/calayer/1410817-anchorpoint)
        self.layer.anchorPoint = CGPoint(x: 0.5, y: 0.5)
    }
}

FlowLayout:

/// Custom `UICollectionViewFlowLayout` that provides the flowlayout information like paging and `CardCell` movements.
class VerticalCardSwiperFlowLayout: UICollectionViewFlowLayout {

    /// This property sets the amount of scaling for the first item.
    public var firstItemTransform: CGFloat?
    /// This property enables paging per card. The default value is true.
    public var isPagingEnabled: Bool = true
    /// Stores the height of a CardCell.
    internal var cellHeight: CGFloat!

    override func prepare() {
        super.prepare()

        assert(collectionView!.numberOfSections == 1, "Number of sections should always be 1.")
        assert(collectionView!.isPagingEnabled == false, "Paging on the collectionview itself should never be enabled. To enable cell paging, use the isPagingEnabled property of the VerticalCardSwiperFlowLayout instead.")
    }

    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        let items = NSArray (array: super.layoutAttributesForElements(in: rect)!, copyItems: true)

        items.enumerateObjects(using: { (object, index, stop) -> Void in
            let attributes = object as! UICollectionViewLayoutAttributes

            self.updateCellAttributes(attributes)
        })
        return items as? [UICollectionViewLayoutAttributes]
    }

    /**
     Updates the attributes.
     Here manipulate the zIndex of the cards here, calculate the positions and do the animations.
     - parameter attributes: The attributes we're updating.
    */
    func updateCellAttributes(_ attributes: UICollectionViewLayoutAttributes) {
        let minY = collectionView!.bounds.minY + collectionView!.contentInset.top
        let maxY = attributes.frame.origin.y

        let finalY = max(minY, maxY)
        var origin = attributes.frame.origin
        let deltaY = (finalY - origin.y) / attributes.frame.height

        if let itemTransform = firstItemTransform {
            let scale = 1 - deltaY * itemTransform
            attributes.transform = CGAffineTransform(scaleX: scale, y: scale)
            // TODO: add card stack effect (like Shazam)
        }
        origin.y = finalY
        attributes.frame = CGRect(origin: origin, size: attributes.frame.size)
        attributes.zIndex = attributes.indexPath.row
    }

    // We invalidate the layout when a "bounds change" happens, for example when we scale the top cell. This forces a layout update on the flowlayout.
    override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
        return true
    }

    // Cell paging
    override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {

        // If the property `isPagingEnabled` is set to false, we don't enable paging and thus return the current contentoffset.
        guard isPagingEnabled else {
            let latestOffset = super.targetContentOffset(forProposedContentOffset: proposedContentOffset, withScrollingVelocity: velocity)
            return latestOffset
        }

        // Page height used for estimating and calculating paging.
        let pageHeight = cellHeight + self.minimumLineSpacing

        // Make an estimation of the current page position.
        let approximatePage = self.collectionView!.contentOffset.y/pageHeight

        // Determine the current page based on velocity.
        let currentPage = (velocity.y < 0.0) ? floor(approximatePage) : ceil(approximatePage)

        // Create custom flickVelocity.
        let flickVelocity = velocity.y * 0.3

        // Check how many pages the user flicked, if <= 1 then flickedPages should return 0.
        let flickedPages = (abs(round(flickVelocity)) <= 1) ? 0 : round(flickVelocity)

        // Calculate newVerticalOffset.
        let newVerticalOffset = ((currentPage + flickedPages) * pageHeight) - self.collectionView!.contentInset.top

        return CGPoint(x: proposedContentOffset.x, y: newVerticalOffset)
    }

    override func finalLayoutAttributesForDisappearingItem(at itemIndexPath: IndexPath) -> UICollectionViewLayoutAttributes? {

        // make sure the zIndex of the next card is higher than the one we're swiping away.
        let nextIndexPath = IndexPath(row: itemIndexPath.row + 1, section: itemIndexPath.section)
        let nextAttr = self.layoutAttributesForItem(at: nextIndexPath)
        nextAttr?.zIndex = nextIndexPath.row

        // attributes for swiping card away
        let attr = self.layoutAttributesForItem(at: itemIndexPath)

        return attr
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...