Shuffle iOS Library

repository·master·Indexed 21 days ago

https://github.com/mac-gallagher/shuffle

An iOS library for creating fluid card stack interfaces featuring swipe recognition, overlays, and programmatic controls. It provides SwipeCard and SwipeCardStack components, supporting custom content, footers, and direction-specific overlays. The library includes a data source and delegate pattern for managing card stacks, as well as methods for programmatic swiping, shifting, undoing, and dynamic insertion or deletion of cards.

Tokens
2.2K
Snippets
5
Records
8
Agent score
26%

What's inside Shuffle

  1. Difference between index and position in insertCard

    master

    When calling insertCard(atIndex:position:), it is critical to distinguish between the two parameters:

    1. index: Refers to the index of the card/model in your data source (e.g., your [CardModel] array).
    2. position: Refers to the visual position in the card stack.

    Unlike UITableView, where these are often equivalent, the position in SwipeCardStack is dynamic because it changes as cards are swiped away. Always calculate the position based on the value returned from numberOfRemainingCards() on the SwipeCardStack instance to avoid errors.

  2. Configure SwipeCard layout components

    master

    A SwipeCard is composed of three distinct layers:

    1. Content: The primary view of the card (content: UIView?). This is where your main card template resides.
    2. Footer: An auxiliary view at the bottom of the card (footer: UIView?). It is laid out above the content if transparent, otherwise drawn below it. You can also set footerHeight: CGFloat.
    3. Overlays: Views that react to the user's drag by changing their alpha value. Overlays are always laid out above the footer.

    Use these methods to manage overlays:

    • overlay(forDirection:) -> UIView?
    • setOverlay(_:forDirection:)
    • setOverlays(_: [SwipeDirection: UIView])
  3. Implement an infinite card stack with an external API

    master

    You can create an 'infinite' scrolling effect by fetching new models from a network utility and appending them to both your data source and the SwipeCardStack.

    When using appendCards(atIndices:), you must calculate the range of new indices based on the previous count of your data source.

    To add cards to the top of the stack instead of the bottom, you should insert models into the beginning of your data source and use insertCard(atIndex: 0, position: 0) for each model (ideally in reverse order so the first model in the batch becomes the topmost card).

    // Example: Appending new models to the bottom of the stack
    private func addCards() {
      NetworkUtility.fetchNewCardModels { [weak self] newModels in
        guard let strongSelf = self else { return }
          
        let oldModelsCount = strongSelf.cardModels.count
        let newModelsCount = oldModelsCount + newModels.count
          
        DispatchQueue.main.async {
          strongSelf.cardModels.append(contentsOf: newModels)
            
          let newIndices = Array(oldModelsCount..<newModelsCount)
          strongSelf.cardStack.appendCards(atIndices: newIndices)
        }
      }
    }
  4. Install Shuffle via CocoaPods, Carthage, or Swift Package Manager

    master

    You can integrate Shuffle into your iOS project using several dependency managers:

    CocoaPods

    Add this to your Podfile:

    pod 'Shuffle-iOS'

    Then import it using:

    import Shuffle_iOS

    Carthage

    Add this to your Cartfile:

    github "mac-gallagher/Shuffle"

    Swift Package Manager

    Add the following to your Package.swift dependencies:

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

    Manual Installation

    Download and drop the Sources directory directly into your project.

  5. Implement a basic SwipeCard stack

    master

    To use Shuffle, follow these three steps:

    1. Create a SwipeCard: Subclass SwipeCard or configure an instance. You can set swipeDirections, assign a content view, and set direction-specific overlays.
    2. Setup SwipeCardStack: Add a SwipeCardStack instance to your view hierarchy and set its frame.
    3. Provide Data: Conform to SwipeCardStackDataSource to provide cards via cardStack(_:cardForIndexAt:) and return the total count via numberOfCards(in:).
    // 1. Create a card
    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
    }
    
    // 2. Setup the stack in a ViewController
    class ViewController: UIViewController, SwipeCardStackDataSource {
      let cardStack = SwipeCardStack()
      let cardImages = [UIImage(named: "img1")!, UIImage(named: "img2")!]
    
      override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(cardStack)
        cardStack.frame = view.safeAreaLayoutGuide.layoutFrame
        cardStack.dataSource = self
      }
    
      // 3. Implement DataSource
      func cardStack(_ cardStack: SwipeCardStack, cardForIndexAt index: Int) -> SwipeCard {
        return card(fromImage: cardImages[index])
      }
    
      func numberOfCards(in cardStack: SwipeCardStack) -> Int {
        return cardImages.count
      }
    }
  6. Insert and delete cards in SwipeCardStack

    master

    To update a SwipeCardStack dynamically (e.g., when fetching new data from an API), use the following methods on SwipeCardStack.

    Insertion Methods

    • insertCard(atIndex: Int, position: Int): Inserts a single card.
      • index: The index of the card/model in your underlying data source.
      • position: The visual position in the stack. This is dynamic and depends on how many cards remain in the stack. To ensure accuracy, calculate this using the numberOfRemainingCards() method.
    • appendCards(atIndices: [Int]): Appends multiple cards to the bottom of the stack. The indices refer to the indices in your data source.

    Deletion Methods

    • deleteCards(atIndices: [Int]): Deletes cards based on their index in the data source.
    • deleteCards(atPositions: [Int]): Deletes cards based on their current visual position in the stack.
    // Insertion
    func insertCard(atIndex index: Int, position: Int)
    func appendCards(atIndices indices: [Int])
    
    // Deletion
    func deleteCards(atIndices indices: [Int])
    func deleteCards(atPositions positions: [Int])
  7. Perform programmatic actions on SwipeCardStack

    master

    Use these methods on a SwipeCardStack instance to control the stack programmatically:

    • Swipe: swipe(_:animated:) performs a swipe in the specified SwipeDirection.
    • Shift: shift(withDistance:animated:) moves the stack forward by a specific number of cards (skipping already swiped cards). Defaults to a distance of 1.
    • Undo: undoLastSwipe(animated:) brings the most recently swiped card back to the top.
    // Example programmatic calls
    cardStack.swipe(.left, animated: true)
    cardStack.shift(withDistance: 2, animated: true)
    cardStack.undoLastSwipe(animated: true)
  8. Subscribe to SwipeCardStack events

    master

    Conform to SwipeCardStackDelegate to handle user or programmatic interactions. The following methods are available:

    • cardStack(_:didSelectCardAt:): Triggered when a card is selected.
    • cardStack(_:didSwipeCardAt:with:): Triggered when a card is swiped in a specific SwipeDirection. Note: This is called for both user and programmatic swipes.
    • cardStack(_:didUndoCardAt:from:): Triggered when an undo action is performed.
    • didSwipeAllCards(_:): Triggered when the stack is empty.