JoniVR / VerticalCardSwiper

A marriage between the Shazam Discover UI and Tinder, built with UICollectionView in Swift.
MIT License
1.4k stars 101 forks source link

[Feature] Swipe without removing the item from the list #69

Open james-ff opened 4 years ago

james-ff commented 4 years ago

New Issue Checklist

Is your feature request related to a problem? Please describe.

If I do not remove the swiped item from the datasource an error occurs

[UICollectionView] Invalid update: invalid number of items in section 0.  The number of items contained in an existing section after the update (5) must be equal to the number of items contained in that section before the update (5), plus or minus the number of items inserted or deleted from that section (0 inserted, 1 deleted) and plus or minus the number of items moved into or out of that section (0 moved in, 0 moved out). - will perform reloadData. UICollectionView instance: <VerticalCardSwiper.VerticalCardSwiperView: 0x7fd828086000; baseClass = UICollectionView; frame = (0 0; 414 673); clipsToBounds = YES; userInteractionEnabled = NO; gestureRecognizers = <NSArray: 0x600001be2c40>; layer = <CALayer: 0x600001590c20>; contentOffset: {-20, -40}; contentSize: {374, 2875}; adjustedContentInset: {40, 20, 90, 20}; layout: <VerticalCardSwiper.VerticalCardSwiperFlowLayout: 0x7fd82757c8d0>; dataSource: <VerticalCardSwiper.VerticalCardSwiper: 0x7fd82757c0c0; frame = (0 0; 414 673); autoresize = RM+BM; layer = <CALayer: 0x6000015904c0>>>; currentUpdate: [UICollectionViewUpdate - 0x7fd82bd21980: old:<UICollectionViewData: 0x600002d89180> new<UICollectionViewData: 0x600002d07800> items:<(
    "D(0,0)"
)>]

Describe the solution you'd like

I would like to configure the cardSwiper view to not remove an item when it is swiped (instead trigger an action), and later on when that action finishes I can remove the item programmatically.

Describe alternatives you've considered

I've considered re-adding the removed item immediately, but It feels like this would be a hack solution, which might cause strange animations

Additional context

none

alobanov11 commented 4 years ago

Hi @james-ff, I had the same error. It was a stupid mistake:

private var films = [Film]() {
    didSet { self.cardSwiper.reloadData() }
}
...
func willSwipeCardAway(card: CardCell, index: Int, swipeDirection: SwipeDirection) {
    guard index < self.films.count else { return }
    self.films.remove(at: index)
}

I just removed didSet and it worked.

JoniVR commented 4 years ago

Do you want the card to swipe away without having to update the datasource to reflect this? Or just block the card from swiping away?

To me personally it seems weird to want your datasource out of sync with the actual displayed data, I think this is why Apple does this too? I'm just forwarding the UICollectionView for the most part anyways.

james-ff commented 4 years ago

Blocking the card from swiping away matches my intent here.

Hope that clarifies it. In the meantime I add the item back in if the confirmation is cancelled.

sevgjan commented 4 years ago

Hi @james-ff , I was able to achieve this by modifying internal func didSwipeAway method in VerticalCardSwiper.swift class, from:

 internal func didSwipeAway(cell: CardCell, swipeDirection direction: SwipeDirection) {
        if let indexPathToRemove = self.verticalCardSwiperView.indexPath(for: cell) {
            swipedCard = nil
            self.verticalCardSwiperView.performBatchUpdates({
                self.verticalCardSwiperView.deleteItems(at: [indexPathToRemove])
            }, completion: { [weak self] _ in
                self?.verticalCardSwiperView.collectionViewLayout.invalidateLayout()
                self?.verticalCardSwiperView.isUserInteractionEnabled = true
                self?.delegate?.didSwipeCardAway?(card: cell, index: indexPathToRemove.row, swipeDirection: direction)
            })
        }
    }

to:

internal func didSwipeAway(cell: CardCell, swipeDirection direction: SwipeDirection) {
        if let indexPathToRemove = self.verticalCardSwiperView.indexPath(for: cell) {
            swipedCard = nil
            self.verticalCardSwiperView.performBatchUpdates({
                if direction == .left {
                    self.verticalCardSwiperView.deleteItems(at: [indexPathToRemove])
                }
            }, completion: { [weak self] _ in
                self?.verticalCardSwiperView.collectionViewLayout.invalidateLayout()
                self?.verticalCardSwiperView.isUserInteractionEnabled = true
                self?.delegate?.didSwipeCardAway?(card: cell, index: indexPathToRemove.row, swipeDirection: direction)
            })
        }
    }

Hope this helps.