// TODO
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.
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)
}
}
}
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
.
// TODO