JoniVR / VerticalCardSwiper

A marriage between the Shazam Discover UI and Tinder, built with UICollectionView in Swift.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[Feature] Swipe without removing the item from the list

james-ff opened this issue · comments

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

commented

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.

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.

Blocking the card from swiping away matches my intent here.

  • The idea being that swiping one direction could remove an item, and swiping the other would bring up a confirmation view.
  • The card would then slide back into position while a confirmation view appears and stay there if cancelled
  • In the case of confirmation the card could then be removed programmatically

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

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.