Skip to content

Latest commit

History

History
128 lines (96 loc) 路 4.39 KB

AdvancedUsage.md

File metadata and controls

128 lines (96 loc) 路 4.39 KB

Advanced Usage

Animations

// TODO

Inserting and Deleting Cards

If you're using an external API to retrieve your SwipeCard data models, chances are you'll need to update the card stack occasionally as new models come in. As of version 0.4.0, Shuffle includes the following methods on SwipeCardStack:

func insertCard(atIndex index: Int, position: Int)
func appendCards(atIndices indices: [Int]) // Index refers to the index of the card in the data source
func deleteCards(atIndices indices: [Int])
func deleteCards(atPositions positions: [Int]) // Position refers to the position of the card in the stack

Using the insert methods in particular, we can give the illusion of an "infinite" card stack. Let's look at an example.

External API Example

Suppose we have a utility which fetches raw data models and decodes them into an array of CardModels:

struct NetworkUtility {
  static func fetchNewCardModels(@escaping completion: ([CardModel]) -> ()) {
    // Decode network models into array of CardModels and return result in completion block
  }
}

The following view controller displays a SwipeCardStack and holds a reference to the card models. In this example, the new models are fetched and added to the card stack after every 10 swipes:

class ViewController: UIViewController: SwipeCardStackDataSource, SwipeCardStackDelegate {

  let cardStack = SwipeCardStack()
  var cardModels: [CardModel]
  var swipedCount: Int = 0
	
  func viewDidLoad() {
    super.viewDidLoad()
    cardStack.dataSource = self
    cardStack.delegate = self
    
    // Layout cardStack on view
    
    addCards()
  }
	
  // MARK: SwipeCardStackDataSource
	
  func numberOfCards(in cardStack: SwipeCardStack) -> Int {
    return cardModels.count
  }
	
  func cardStack(_ cardStack: SwipeCardStack, cardForIndexAt index: Int) -> SwipeCard {
    let card = SwipeCard()
    card.model = cardModels[index]
    return card
  }
	
  // MARK: SwipeCardStackDelegate
	
  func didSwipeCard(atIndex index: Int) {
    swipedCount += 1
    if swipedCount % 10 == 0 {
      addCards()
    }
  }
	
  private func addCards() {
    NetworkUtility.fetchNewCardModels { [weak self] newModels in
      guard let strongSelf = self else { return }
        
      let oldModelsCount = strongSelf.cardModels.count
      let newModelsCount = oldModelCount + newModels.count
        
      DispatchQueue.main.async {
        strongSelf.cardModels.append(contentsOf: newModels)
          
        let newIndices = Array(oldModelsCount..<newModelsCount)
        strongSelf.cardStack.appendCards(atIndices: newIndices)
      }
    }
  }
}

Here, we use the appendCards: method to append the new cards to the bottom of the card stack. If instead we wanted to add the cards to the top of the stack, we could do something like

NetworkUtility.fetchNewCardModels { [weak self] newModels in
  guard let strongSelf = self else { return }
	
  DispatchQueue.main.async {
    // Insert in reverse order so that newModels.first is the model for the topmost card
    for model in newModels.reversed() {
      strongSelf.cardModels.insert(model, at: 0)
      strongSelf.cardStack.insertCard(atIndex: 0, position: 0)
    }
  }
}

Common pitfalls

If you are familar with the common pitfalls of the insert/delete row methods on UITableView, the exact same pitfalls apply here. There is, however, one crucial difference between the two insert methods:

// UITableView
func insertRows(at indexPaths: [IndexPath], with animation: UITableView.RowAnimation)
// SwipeCardStack
func insertCard(atIndex: Int, position: Int)

The insertCard method has an additional position parameter which represents the inserted position in the card stack, whereas the index parameter represents the index of the card/model in the data source. The position is dynamic based on the number of cards remaining (not swiped) in the stack. Note: For a UITableView, the indexPath and index/position parameters are equavalent.

Since the number of remaining cards is subject to change, be sure to calculate the position based on the value returned from the numberOfRemainingCards: method on SwipeCardStack.

Swipe Recognition

// TODO