Прежде всего, извините, что это не очень общий вопрос (он довольно специфичен), но я стучу головой об стену и не могу ничего найти (очевидно).
Итак, я построил эту библиотеку, чтобы смахивать стопку карт на основе UICollectionView
, и все работает отлично, за исключением , когда я нажимаю на строку состояния, чтобы «прокрутить вверх», а затем пытаюсь чтобы убрать карту, анимация как-то делает что-то странное Я не могу ни объяснить, ни понять.
Еще одна вещь, которая случается в нижней части анимации, также не работает
Вы можете найти исходный код на Github здесь
Вот проблема Github с дополнительной информацией:
https://github.com/JoniVR/VerticalCardSwiper/issues/4
Любая помощь по этому вопросу будет принята с благодарностью!
Хорошего дня:)
вот демо:
Вот некоторые из наиболее важных фрагментов кода (я думаю):
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
}
}