Giter Club home page Giter Club logo

shuffle's Introduction


Platform Swift Version Build Status Swift Package Manager CocoaPods Carthage Code Coverage LICENSE

Made with ❤️ by Mac Gallagher

Features

💡 Advanced swipe recognition based on velocity and card position

💡 Manual and programmatic actions

💡 Smooth card overlay view transitions

💡 Fluid and customizable animations

💡 Dynamic card loading using data source pattern

Example

To run the example project, clone the repo and run the ShuffleExample target.

Basic Usage

  1. Create your own card by either subclassing SwipeCard or setting its properties directly:

    func card(fromImage image: UIImage) -> SwipeCard {
      let card = SwipeCard()
      card.swipeDirections = [.left, .right]
      card.content = UIImageView(image: image)
      
      let leftOverlay = UIView()
      leftOverlay.backgroundColor = .green
      
      let rightOverlay = UIView()
      rightOverlay.backgroundColor = .red
      
      card.setOverlays([.left: leftOverlay, .right: rightOverlay])
      
      return card
    }

    The card returned from card(fromImage:) displays an image, can be swiped left or right, and has overlay views for both directions.

  2. Initialize your card data and place a SwipeCardStack on your view:

    class ViewController: UIViewController {
      let cardStack = SwipeCardStack()
      
      let cardImages = [
          UIImage(named: "cardImage1"),
          UIImage(named: "cardImage2"),
          UIImage(named: "cardImage3")
      ]
      
      override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(cardStack)
        cardStack.frame = view.safeAreaLayoutGuide.layoutFrame
      }
    }
  3. Conform your class to SwipeCardStackDataSource and set your card stack's dataSource:

    func cardStack(_ cardStack: SwipeCardStack, cardForIndexAt index: Int) -> SwipeCard {
      return card(fromImage: cardImages[index])
    }
    
    func numberOfCards(in cardStack: SwipeCardStack) -> Int {
      return cardImages.count
    }
    cardStack.dataSource = self
  4. Conform to SwipeCardStackDelegate to subscribe to any of the following events:

     func cardStack(_ cardStack: SwipeCardStack, didSelectCardAt index: Int)
     func cardStack(_ cardStack: SwipeCardStack, didSwipeCardAt index: Int, with direction: SwipeDirection)
     func cardStack(_ cardStack: SwipeCardStack, didUndoCardAt index: Int, from direction: SwipeDirection)
     func didSwipeAllCards(_ cardStack: SwipeCardStack)

    Note: didSwipeCardAt and didSwipeAllCards are called regardless if a card is swiped programmatically or by the user.

Card Stack Actions

The following methods are available on SwipeCardStack.

Swipe

Performs a swipe programmatically in the given direction.

func swipe(_ direction: SwipeDirection, animated: Bool)

Shift

Shifts the card stack's cards by the given distance. Any swiped cards are skipped over.

func shift(withDistance distance: Int = 1, animated: Bool)

Undo

Returns the most recently swiped card to the top of the card stack.

func undoLastSwipe(animated: Bool)

Card Layout

Each SwipeCard consists of three UI components: its content, footer, and overlay(s).

Content

The content is the card's primary view. You can include your own card template here.

var content: UIView? { get set }

Footer

The footer is an axuilliary view on the bottom of the card. It is laid out above the content in the view hierarchy if the footer is transparent. Otherwise, the footer is drawn just below the content.

var footer: UIView? { get set }
var footerHeight: CGFloat { get set }

Overlays

An overlay is a view whose alpha value reacts to the user's dragging. The overlays are laid out above the footer, regardless if the footer is transparent or not.

func overlay(forDirection direction: SwipeDirection) -> UIView?
func setOverlay(_ overlay: UIView?, forDirection direction: SwipeDirection)
func setOverlays(_ overlays: [SwipeDirection: UIView])

Advanced Usage

For more advanced usage, including

visit the document here.

Installation

CocoaPods

Shuffle is available through CocoaPods. To install it, simply add the following line to your Podfile:

pod 'Shuffle-iOS'

The import statement in this case will be

import Shuffle_iOS

Carthage

Shuffle is available through Carthage. To install it, simply add the following line to your Cartfile:

github "mac-gallagher/Shuffle"

Swift Package Manager

Shuffle is available through Swift PM. To install it, simply add the package as a dependency in Package.swift:

dependencies: [
  .package(url: "https://github.com/mac-gallagher/Shuffle.git", from: "0.1.0"),
]

Manual

Download and drop the Sources directory into your project.

Requirements

  • iOS 9.0+
  • Xcode 10.2+
  • Swift 5.0+

Apps Using Shuffle

We love to hear about apps that use Shuffle - feel free to submit a pull request and share yours here!


Made with ❤️ by Mac Gallagher

shuffle's People

Contributors

fabriceortega avatar hb2708 avatar mac-gallagher avatar pablosproject avatar pilgwon avatar sharadchauhan0504 avatar stanng2020 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

shuffle's Issues

One unresponsive image appears at top of stack

Screen Shot 2020-07-13 at 01 14 48
Describe the bug
I think it might have something to to with adding images dynamically. I am doing it this way:

self.cardImages.append(loadedImg)                    
let newIndices = Array(self.cardImages.count-1..<self.cardImages.count)
self.cardStack.appendCards(atIndices: newIndices)

Won't compile with SPM

Describe the bug
I imported the latest Shuffle framework with the Swift Package Manager and I get several errors like "Use of undeclared type 'CGRect'" or "Use of undeclared type 'CGFloat'", which makes sense since CGStuff is in CoreGraphics, not in Foundation. Am I missing something with the SPM ? Thanks for your help ;-)

To Reproduce
Add the dependency for Shuffle and compile

Expected behavior
The code should compile

Screenshots
Uploading Capture d’écran 2020-03-25 à 15.15.01.png…

Additional context
Add any other context about the problem here.

Only two cards are displayed

Describe the bug
A clear and concise description of what the bug is.

I am running the Shuffle/Example code on my iPhone, ( iphone 11, iOS latest), and only two cards are displayed at a time. Moreover, even though the delegates are set as:

        cardStack.delegate = self
        cardStack.dataSource = self
        buttonStackView.delegate = self

It seems like only the dataSource delegate is actually responsive, because the delegate functions:

    didSwipeAllCards
   cardStack

do not respond to user-generated events.

To Reproduce
Steps to reproduce the behavior:

  1. Go to 'Example'
  2. Run example
  3. Swipe twice
  4. See no more cards

Expected behavior
A clear and concise description of what you expected to happen:
Example gives you 7 cards in the datasource, so there should be 7 cards. And on card swipe, cardStack should be called, after all cards are swiped, didSwipeAllCards should be called.

Screenshots
If applicable, add screenshots to help explain your problem.

Additional context
Add any other context about the problem here.

Disable user interaction on overlay views

Disable user interaction on overlay views is not released through Pods. pod version still have user interaction ON for overlay container.
You can check this by installing lib through pods

Storyboard Support

The plan here is to add IBInspectable/IBDesignable support for both SwipeCard and SwipeCardStack so they work well with Storyboards. A similar enhancement was done here on my other repo MultiProgressView.

It would be great to add a new example using storyboards as well (again, check MultiProgressView for some inspo). Ideally, it would look like something that belongs in a production app!

Allow card movement to swipe only defined directions

Is your feature request related to a problem? Please describe.
When swipe the cards I can move in any directions, I want to be able to move the card only to the configured direction.

Describe the solution you'd like
Only allow (if user prefer) the configured swipe directions. [left] The card movement allowed to left, he don't move to right or up.

Describe alternatives you've considered
Added an property to allow card animation to vertical and horizontal.

Stack At Top

Can we have stack showing at top?
one workaround i have considered is to add artificially created stack like in following picture. but do have some better way to handle that?
also i have to use this as part of cell for UITableView, do you think it can create adverse effect?

Simulator Screen Shot - iPhone 11 Pro Max - 2019-10-22 at 13 27 24

Improve Spring Animations

UIView.animateWithDuration:delay:usingSpringWithDamping: is notorious for looking pretty bad. There are many libraries out there to improve the realism of the spring animation, but my favorite solution is this one created by @jenox. It would be great if the current spring animation logic was replaced with this.

Swipe Card Temporarily Pauses After releasing Drag Gesture

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

Yes, currently when you swipe the card (left or right), a slight pause occurs after releasing the drag gesture. After releasing the drag gesture, the card stops abruptly then continues going in the direction the user swiped. This leads to a poorer user experience because it disobeys physics and the expected card movement the user is predicting.

Describe the solution you'd like

Make it so by default, after the user has crossed a certain location on the X axis (Positive and Negative) the card will automatically continue moving with its current speed/momentum even when the user releases the Drag Gesture.

Swipe Action

If we want to just show the next element on right swipe and previous with a left swipe. Then what will we need to do?

Well Done Sir!

Excuse the bug report ;)

I tried a number of these type of components and yours is by far the best. Good job!

Append card without UI interference

Hi,

from time to time I am appending some cards to the cardStack with the cardModels.append(newModel) method.
As you have written in the docs i have to update the card stack occasionally.
But if I update the cardstack with cardStack.reloadData() in the same moment as a user is dragging the top card in the UI i.e a little bit to the right side so that the overlay is visible (but didn't finished the swipe) then the top card catches back to the middle automatically.
Is there any possibility to avoid that ? Maybe reloading the cardstack in the background without interfering the UI?

Custom card stack layouts

While I personally have no plans to implement this, I am open to PRs which add this functionality. Making sure the animations work properly will probably be challenging.

deleteCards crashes with fatal error

App Crash with deleteCards function
If you use the func deleteCards(atPositions positions: [Int]) an fatal error appears:

Fatal error: Invalid update: invalid number of cards. The number of cards contained in the card stack after the update (7) must be equal to the number of cards contained in the card stack before the update (7), plus or minus the number of cards inserted or deleted (0 inserted, 1 deleted): file Shuffle-master/Sources/Shuffle/SwipeCardStack/SwipeCardStack.swift, line 430
2020-08-21 20:56:36.170087+0200 ShuffleExample[28610:461963] Fatal error: Invalid update: invalid number of cards. The number of cards contained in the card stack after the update (7) must be equal to the number of cards contained in the card stack before the update (7), plus or minus the number of cards inserted or deleted (0 inserted, 1 deleted): file Shuffle-master/Sources/Shuffle/SwipeCardStack/SwipeCardStack.swift, line 430
(lldb)

Reproducing Steps

  1. Go to "TinderViewController.swift in the Demo App
  2. Add cardStack.deleteCards(atPositions: [3]) in a didTapButton action (i.e. swipe right)
  3. Crash!

What I expected
The cardModel at Position [3] will be deleted from the cardStack.

Investigate layer rasterization performance improvements

Curious to know whether setting

layer.shouldRasterize = true
layer.rasterizationScale = UIScreen.main.scale

on the swipe cards provides any performance improvements. Since the overlay views animate their alpha value, the answer is not cut-and-dry to me. Maybe set it only on certain subviews?Should profile with Core Animation instrument.

Animation completed callback or cardForItem(at: IndexPath) function?

I ❤️ Shuffle! This issue is usage-related, not a bug or a feature. I want to trigger an event when the card is swiped right but I'm encountering two separate issues.

  1. In func cardStack(_ cardStack: SwipeCardStack, didSwipeCardAt index: Int, with direction: SwipeDirection) there's no callback on when the animation is completed. I can't seem to trigger an undo until the animation completes if I want to display the last card the user swiped.
  2. I can't figure out how to get the card at the current index. I'm looking for something similar to the UITableView function cellForRow(at: IndexPath) so I can fetch the card being show to trigger a visual event on it, such as displaying an overlay.

I don't see any of these delegate methods exposed anywhere in the library. Are either of these things possible?

Do not dispose cards, instead shift on swipe

Is your feature request related to a problem? Please describe.
I would like to do something like memorise the order of cards. Therefore it would be good to disable disposal and just send them to the back.

Describe the solution you'd like
An option to disable the disposal.

Describe alternatives you've considered
Re-adding the card when getting notified to is has been swiped out. But this will break the undo. and make the handling somehow wired.

Crash: appendCards method

Describe the bug

  • appendCards method is getting crashed.
    I am using a dummy model (array of String). Initially, there are 3 elements. I am using DispatchQueue.main.asyncAfter to add more data.

Error
Precondition failed: Attempt to delete card at index 105553146154112, but there are only 3 cards before the update: file /Users/apple/Documents/Infino Factory/Pods/Shuffle-iOS/Sources/Shuffle/SwipeCardStack/CardStackStateManager.swift, line 82

Code

DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
      let newModel             = ["dasd", "dasdad", "dsadsad"]
      let oldModelsCount   = self.dummyModel.count
      let newModelsCount = oldModelsCount + newModel.count
      self.dummyModel.append(contentsOf: newModel)
      let newIndices           = Array(oldModelsCount..<newModelsCount)
      self.cardStack.appendCards(atIndices: newIndices)
}

Header not precised in the README

I downloaded the library.
I tried to use it but Xcode did not recognized SwipeCard.
It was not written on the README: please let the users know they need to import Shuffle_iOS in order to use the library

Thank you

Property topCardIndex isn't updated immediately

After I swiped a card the method func cardStack(_ cardStack: SwipeCardStack, didSwipeCardAt index: Int, with direction: SwipeDirection) of SwipeCardStackDelegate is called.

And printing topCardIndex property of SwipeCardStack object there still returns 0, however, it just has been swiped, so index 1 expected.

Question

Hi there! Thanks for making Shuffle!

I'm having an issue related to #13. I'm trying to periodically add to content in the SwipeCardStack. I'm having to do some fancy stuff with updating the datasource.

Anyways my problem is more specific. If I'm swiping a card and then call reloadData() the animations conflict and the cards get stuck in an isUserInteractionEnabled == false state. It looks like special care has been taken to disable isUserInteractionEnabled for every swipe. Were you experiencing specific issues when not specifically disabling isUserInteractionEnabled?

func card(didSwipe card: SwipeCard, with direction: SwipeDirection, forced: Bool) {
        delegate?.cardStack?(self, didSwipeCardAt: topCardIndex, with: direction)
        ...
        isUserInteractionEnabled = false
        animator.swipe(self, topCard: card, direction: direction, forced: forced)
    }

Please let me know! Thanks,

  • Nick

Storyboard

Hello,

Thanks for your lib.
I wanted to ask something, is that possible to use it with the storyboard?
Can you provide an example on your github as well please ?

Make some extra space at the top

Hi all,
first I want to thank you very much for that really great work!
I am currently struggling with the layout of the card + footer.
What I want to do is to add some additional buttons at the top of the view.
But to do this I have to change the height of the picture to make some extra space available at the top.
Unfortunately I am unable to find the right parameter to handle this without changing additional things like footer height, picture width..
Can anyone help me ?
Thanks!

Card swipe has an after image at the top left corner

Describe the bug

I am adding a SwiftUI view as the SwipeCard content but there is an afterimage at the top left corner after each swipe. I wonder if this is caused by how the card is collected and recycled for later usage?

    class Coordinator: NSObject,
        SwipeCardStackDataSource,
        SwipeCardStackDelegate
    {
        /// `SwipeCardStackDataSource` required methods
        func cardStack(
            _ cardStack: SwipeCardStack,
            cardForIndexAt index: Int
        ) -> SwipeCard
        {
            return card(fromText: "TEST")
        }
        
        func numberOfCards(in cardStack: SwipeCardStack) -> Int {
            return 10000
        }
        
        func card(fromText text: String) -> SwipeCard {
            let card = SwipeCard()
            card.swipeDirections = [.left, .right]
            
            /// How to use UIHostingController inside an UIView or as an UIView?
            /// https://stackoverflow.com/q/56819063/2226315
            let cardView = CustomCardView()
            let content = UIHostingController(rootView: cardView)
            content.view.backgroundColor = nil
            card.content = content.view
            
            return card
        }
    }

Screenshots
If applicable, add screenshots to help explain your problem.

20200912@221408

Additional context
Add any other context about the problem here.

In SwiftUI card swipe produce an afterimage at the top left corner

Describe the bug

I am adding a SwiftUI view as the SwipeCard content but there is an afterimage at the top left corner after each swipe. I wonder if this is caused by how the card is collected and recycled for later usage?

    class Coordinator: NSObject,
        SwipeCardStackDataSource,
        SwipeCardStackDelegate
    {
        /// `SwipeCardStackDataSource` required methods
        func cardStack(
            _ cardStack: SwipeCardStack,
            cardForIndexAt index: Int
        ) -> SwipeCard
        {
            return card(fromText: "TEST")
        }
        
        func numberOfCards(in cardStack: SwipeCardStack) -> Int {
            return 10000
        }
        
        func card(fromText text: String) -> SwipeCard {
            let card = SwipeCard()
            card.swipeDirections = [.left, .right]
            
            /// How to use UIHostingController inside an UIView or as an UIView?
            /// https://stackoverflow.com/q/56819063/2226315
            let cardView = CustomCardView()
            let content = UIHostingController(rootView: cardView)
            content.view.backgroundColor = nil
            card.content = content.view
            
            return card
        }
    }

Screenshots
If applicable, add screenshots to help explain your problem.

20200912@221408

Additional context
Add any other context about the problem here.

Undo action is allowed before swipe animation has completed on last card

Steps to reproduce:

  • (Optional) Toggle slow animations on the simulator (⌘ + T)
  • Programmatically swipe the last card on the card stack
  • Quickly trigger an undo action

Expected behavior

It should not allow an undo action until the last card has finished its swipe animation

Actual behavior

It is possible to trigger an undo action while the last card is still animating. It is even possible to see two copies of the card at the same time

Using Shuffle in UITableView/UICollectionView

Describe the bug
TableView and CollectionView don't scroll vertically if the shuffle is in any cell.

To Reproduce
Steps to reproduce the behavior:
Add SwipeCardStack inside TableViewCell/CollectionViewCell and scroll tableView vertically.

Expected behavior
If I chose to move horizontally, Shuffle don't be able to scroll vertically.

Using An Infinite Number of Cards

Is there a way to make an unlimited swipe stack, as Tinder does? Can the number of cards be updated dynamically?


I've tried returning Int.max in numberOfCards, and for testing, I do a modulus on the cardForIndexAt parameter as such: return MyCard(image: cardImages[index % numImages])

This doesn't work out as it seems to load all images in the stack, so the application just crashes when trying to load a practically infinite number of UIViews.

Add insertCards method

Right now, there is only insertCard:atIndex:Int at:Int. It would be nice to have something like insertCards:atIndices: [Int] at: [Int]

Can multiple SwipeCardStack be used in the same project?

Is your feature request related to a problem? Please describe.
I tried to use multiple SwipeCardStack in the same project. It worked in the first one, but when I go to second one, It lost the first stack statuses. CardStackStateManager is shared (Singleton).

Describe the solution you'd like
There is a convenience init function but it is not public.

Are there any way to support multiple SwipeCardStack?

Project can not be archived due to duplicate issue

Project compiles well while debugging on simulator or phone but while compiling getting following issue

Unexpected duplicate tasks:

  1. Target 'Shuffle-iOS' (project 'Pods') has copy command from ‘App Path/Pods/Shuffle-iOS/Sources/Shuffle.plist' to 'Library/Developer/Xcode/DerivedData/IAGeneralApp-arlhwwyhekielgcemsylmtysvpga/Build/Intermediates.noindex/ArchiveIntermediates/IAGeneralApp/IntermediateBuildFilesPath/UninstalledProducts/iphoneos/Shuffle_iOS.framework/Shuffle.plist'
  2. Target 'Shuffle-iOS' (project 'Pods') has copy command from 'App Path/Pods/Shuffle-iOS/Sources/Shuffle.plist' to '/Library/Developer/Xcode/DerivedData/IAGeneralApp-arlhwwyhekielgcemsylmtysvpga/Build/Intermediates.noindex/ArchiveIntermediates/IAGeneralApp/IntermediateBuildFilesPath/UninstalledProducts/iphoneos/Shuffle_iOS.framework/Shuffle.plist'

Please note that when I archived using legacy build system it archived successfully, there is some compatibility issue with new build system

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.