Skip to content

Commit

Permalink
add card:ForIndexAt and position:ForCardAtIndex methods (#87)
Browse files Browse the repository at this point in the history
  • Loading branch information
mac-gallagher authored Jul 5, 2020
1 parent ca79c2b commit 52d0e25
Show file tree
Hide file tree
Showing 9 changed files with 210 additions and 121 deletions.
2 changes: 2 additions & 0 deletions Example/TinderExample/TinderViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@ extension TinderViewController: ButtonStackViewDelegate, SwipeCardStackDataSourc
cardStack.swipe(.up, animated: true)
case 4:
cardStack.swipe(.right, animated: true)
case 5:
cardStack.reloadData()
default:
break
}
Expand Down
4 changes: 2 additions & 2 deletions Shuffle-iOS.podspec
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Pod::Spec.new do |s|

s.name = "Shuffle-iOS"
s.version = "0.2.6"
s.version = "0.2.7"
s.platform = :ios, "9.0"
s.summary = "A multi-directional card swiping library inspired by Tinder"

Expand All @@ -13,7 +13,7 @@ s.homepage = "https://github.com/mac-gallagher/Shuffle"
s.documentation_url = "https://github.com/mac-gallagher/Shuffle/tree/master/README.md"
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { "Mac Gallagher" => "jmgallagher36@gmail.com" }
s.source = { :git => "https://github.com/mac-gallagher/Shuffle.git", :tag => "v0.2.6" }
s.source = { :git => "https://github.com/mac-gallagher/Shuffle.git", :tag => "v0.2.7" }

s.swift_version = "5.0"
s.source_files = "Sources/**/*.{h,swift}"
Expand Down
40 changes: 20 additions & 20 deletions Sources/Shuffle/SwipeCardStack/CardStackAnimator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,16 +72,16 @@ class CardStackAnimator: CardStackAnimatable {
removeAllCardAnimations(cardStack)

if !animated {
for (index, card) in cardStack.visibleCards.enumerated() {
card.transform = cardStack.transform(forCardAtIndex: index)
for (position, value) in cardStack.visibleCards.enumerated() {
value.card.transform = cardStack.transform(forCardAtPosition: position)
}
completion?(true)
return
}

// place background cards in old positions
for (index, card) in cardStack.visibleCards.enumerated() {
card.transform = cardStack.transform(forCardAtIndex: index + distance)
for (position, value) in cardStack.visibleCards.enumerated() {
value.card.transform = cardStack.transform(forCardAtPosition: position + distance)
}

// animate background cards to new positions
Expand All @@ -100,8 +100,8 @@ class CardStackAnimator: CardStackAnimatable {
removeBackgroundCardAnimations(cardStack)

if !animated {
for (index, card) in cardStack.visibleCards.enumerated() {
cardStack.layoutCard(card, at: index)
for (position, value) in cardStack.visibleCards.enumerated() {
cardStack.layoutCard(value.card, at: position)
}
completion?(true)
return
Expand Down Expand Up @@ -135,16 +135,16 @@ class CardStackAnimator: CardStackAnimatable {
removeBackgroundCardAnimations(cardStack)

if !animated {
for (index, card) in cardStack.backgroundCards.enumerated() {
cardStack.layoutCard(card, at: index + 1)
for (position, card) in cardStack.backgroundCards.enumerated() {
cardStack.layoutCard(card, at: position + 1)
}
completion?(true)
return
}

// place background cards in old positions
for (index, card) in cardStack.backgroundCards.enumerated() {
card.transform = cardStack.transform(forCardAtIndex: index)
for (position, card) in cardStack.backgroundCards.enumerated() {
card.transform = cardStack.transform(forCardAtPosition: position)
}

// animate background cards to new positions
Expand All @@ -159,37 +159,37 @@ class CardStackAnimator: CardStackAnimatable {
}

func removeAllCardAnimations(_ cardStack: SwipeCardStack) {
cardStack.visibleCards.forEach { $0.removeAllAnimations() }
cardStack.visibleCards.forEach { $0.card.removeAllAnimations() }
}

// MARK: - Animation Keyframes

func addCancelSwipeAnimationKeyFrames(_ cardStack: SwipeCardStack) {
for (index, card) in cardStack.backgroundCards.enumerated() {
let transform = cardStack.transform(forCardAtIndex: index + 1)
for (position, card) in cardStack.backgroundCards.enumerated() {
let transform = cardStack.transform(forCardAtPosition: position + 1)
Animator.addTransformKeyFrame(to: card, transform: transform)
}
}

func addShiftAnimationKeyFrames(_ cardStack: SwipeCardStack) {
for (index, card) in cardStack.visibleCards.enumerated() {
let transform = cardStack.transform(forCardAtIndex: index)
Animator.addTransformKeyFrame(to: card, transform: transform)
for (position, value) in cardStack.visibleCards.enumerated() {
let transform = cardStack.transform(forCardAtPosition: position)
Animator.addTransformKeyFrame(to: value.card, transform: transform)
}
}

func addSwipeAnimationKeyFrames(_ cardStack: SwipeCardStack) {
for (index, card) in cardStack.visibleCards.enumerated() {
for (position, value) in cardStack.visibleCards.enumerated() {
Animator.addKeyFrame {
cardStack.layoutCard(card, at: index)
cardStack.layoutCard(value.card, at: position)
}
}
}

func addUndoAnimationKeyFrames(_ cardStack: SwipeCardStack) {
for (index, card) in cardStack.backgroundCards.enumerated() {
for (position, card) in cardStack.backgroundCards.enumerated() {
Animator.addKeyFrame {
cardStack.layoutCard(card, at: index + 1)
cardStack.layoutCard(card, at: position + 1)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import UIKit
protocol CardStackTransformProvidable {
func backgroundCardDragTransform(for cardStack: SwipeCardStack,
topCard: SwipeCard,
topCardIndex: Int) -> CGAffineTransform
currentPosition: Int) -> CGAffineTransform
func backgroundCardTransformPercentage(for cardStack: SwipeCardStack, topCard: SwipeCard) -> CGFloat
}

Expand All @@ -37,11 +37,11 @@ class CardStackTransformProvider: CardStackTransformProvidable {

func backgroundCardDragTransform(for cardStack: SwipeCardStack,
topCard: SwipeCard,
topCardIndex: Int) -> CGAffineTransform {
currentPosition: Int) -> CGAffineTransform {
let percentage = backgroundCardTransformPercentage(for: cardStack, topCard: topCard)

let currentScale = cardStack.scaleFactor(forCardAtIndex: topCardIndex)
let nextScale = cardStack.scaleFactor(forCardAtIndex: topCardIndex - 1)
let currentScale = cardStack.scaleFactor(forCardAtPosition: currentPosition)
let nextScale = cardStack.scaleFactor(forCardAtPosition: currentPosition - 1)

let scaleX = (1 - percentage) * currentScale.x + percentage * nextScale.x
let scaleY = (1 - percentage) * currentScale.y + percentage * nextScale.y
Expand Down
78 changes: 54 additions & 24 deletions Sources/Shuffle/SwipeCardStack/SwipeCardStack.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@

import UIKit

/// A typealias for a `SwipeCard` and it's corresponding index in the card stack's `dataSource`.
typealias Card = (index: Int, card: SwipeCard)

open class SwipeCardStack: UIView, SwipeCardDelegate, UIGestureRecognizerDelegate {

open var animationOptions: CardStackAnimatableOptions = CardStackAnimationOptions.default
Expand Down Expand Up @@ -53,18 +56,22 @@ open class SwipeCardStack: UIView, SwipeCardDelegate, UIGestureRecognizerDelegat
}

public var topCardIndex: Int? {
return stateManager.remainingIndices.first
return visibleCards.first?.index
}

var numberOfVisibleCards: Int = 2
var visibleCards: [SwipeCard] = []

/// An ordered array containing all pairs of currently visible cards.
///
/// The `Card` at the first position is the topmost `SwipeCard` in the view hierarchy.
var visibleCards: [Card] = []

var topCard: SwipeCard? {
return visibleCards.first
return visibleCards.first?.card
}

var backgroundCards: [SwipeCard] {
return Array(visibleCards.dropFirst())
return Array(visibleCards.dropFirst()).map { $0.card }
}

var isEnabled: Bool {
Expand Down Expand Up @@ -119,24 +126,24 @@ open class SwipeCardStack: UIView, SwipeCardDelegate, UIGestureRecognizerDelegat
override open func layoutSubviews() {
super.layoutSubviews()
cardContainer.frame = layoutProvider.createCardContainerFrame(for: self)
for (index, card) in visibleCards.enumerated() {
layoutCard(card, at: index)
for (position, value) in visibleCards.enumerated() {
layoutCard(value.card, at: position)
}
}

func layoutCard(_ card: SwipeCard, at index: Int) {
func layoutCard(_ card: SwipeCard, at position: Int) {
card.transform = .identity
card.frame = layoutProvider.createCardFrame(for: self)
card.transform = transform(forCardAtIndex: index)
card.isUserInteractionEnabled = index == 0
card.transform = transform(forCardAtPosition: position)
card.isUserInteractionEnabled = position == 0
}

func scaleFactor(forCardAtIndex index: Int) -> CGPoint {
return index == 0 ? CGPoint(x: 1, y: 1) : CGPoint(x: 0.95, y: 0.95)
func scaleFactor(forCardAtPosition position: Int) -> CGPoint {
return position == 0 ? CGPoint(x: 1, y: 1) : CGPoint(x: 0.95, y: 0.95)
}

func transform(forCardAtIndex index: Int) -> CGAffineTransform {
let cardScaleFactor = scaleFactor(forCardAtIndex: index)
func transform(forCardAtPosition position: Int) -> CGAffineTransform {
let cardScaleFactor = scaleFactor(forCardAtPosition: position)
return CGAffineTransform(scaleX: cardScaleFactor.x, y: cardScaleFactor.y)
}

Expand Down Expand Up @@ -195,7 +202,7 @@ open class SwipeCardStack: UIView, SwipeCardDelegate, UIGestureRecognizerDelegat
if (stateManager.remainingIndices.count - visibleCards.count) > 0 {
let bottomCardIndex = stateManager.remainingIndices[visibleCards.count]
if let card = loadCard(at: bottomCardIndex) {
insertCard(card, at: visibleCards.count)
insertCard(Card(bottomCardIndex, card), at: visibleCards.count)
}
}

Expand Down Expand Up @@ -267,22 +274,45 @@ open class SwipeCardStack: UIView, SwipeCardDelegate, UIGestureRecognizerDelegat
isAnimating = false
}

/// Returns the `SwipeCard` at the specified index.
///
/// - Parameter index: The index of the card in the data source.
/// - Returns: The `SwipeCard` at the specified index, or `nil` if the card is not visible or the index is
/// out of range.
public func card(forIndexAt index: Int) -> SwipeCard? {
for value in visibleCards where value.index == index {
return value.card
}
return nil
}

/// Returns the current position of the card at the specified index.
///
/// A returned value of `0` indicates that the card is the topmost card in the stack.
/// - Parameter index: The index of the card in the data source.
/// - Returns: The current position of the card at the specified index, or `nil` if the index if out of range or the
/// card has been swiped.
public func position(forCardAtIndex index: Int) -> Int? {
return stateManager.remainingIndices.firstIndex(of: index)
}

func reloadVisibleCards() {
visibleCards.forEach { $0.removeFromSuperview() }
visibleCards.forEach { $0.card.removeFromSuperview() }
visibleCards.removeAll()

let numberOfCards = min(stateManager.remainingIndices.count, numberOfVisibleCards)
for index in 0..<numberOfCards {
if let card = loadCard(at: stateManager.remainingIndices[index]) {
insertCard(card, at: index)
for position in 0..<numberOfCards {
let index = stateManager.remainingIndices[position]
if let card = loadCard(at: index) {
insertCard(Card(index, card), at: position)
}
}
}

func insertCard(_ card: SwipeCard, at index: Int) {
cardContainer.insertSubview(card, at: visibleCards.count - index)
layoutCard(card, at: index)
visibleCards.insert(card, at: index)
func insertCard(_ value: Card, at position: Int) {
cardContainer.insertSubview(value.card, at: visibleCards.count - position)
layoutCard(value.card, at: position)
visibleCards.insert(value, at: position)
}

func loadCard(at index: Int) -> SwipeCard? {
Expand Down Expand Up @@ -312,10 +342,10 @@ open class SwipeCardStack: UIView, SwipeCardDelegate, UIGestureRecognizerDelegat
}

func card(didContinueSwipe card: SwipeCard) {
for (index, backgroundCard) in backgroundCards.enumerated() {
for (position, backgroundCard) in backgroundCards.enumerated() {
backgroundCard.transform = transformProvider.backgroundCardDragTransform(for: self,
topCard: card,
topCardIndex: index + 1)
currentPosition: position + 1)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@ class MockCardStackTransformProvider: CardStackTransformProvidable {

var testBackgroundCardDragTransform: CGAffineTransform = .identity
var backgroundCardDragTransformCard: SwipeCard?
var backgroundCardDragTransformIndex: Int?
var backgroundCardDragTransformPosition: Int?

func backgroundCardDragTransform(for cardStack: SwipeCardStack,
topCard: SwipeCard,
topCardIndex: Int) -> CGAffineTransform {
currentPosition: Int) -> CGAffineTransform {
backgroundCardDragTransformCard = topCard
backgroundCardDragTransformIndex = topCardIndex
backgroundCardDragTransformPosition = currentPosition
return testBackgroundCardDragTransform
}

Expand Down
Loading

0 comments on commit 52d0e25

Please sign in to comment.