0

I am creating a simple card game (Set) in SwiftUI. I have a button that will deal X new cards when tapped. Currently, it makes all cards show up at once. I was wondering how I could make them come out one at a time.

Deal works by appending a new card to a Deck array in the model. ContentView displays each card in the grid.

This is what I currently have after looking online. Displays first card then next all at once

func deal(_ numberOfCards: Int) {
        withAnimation(Animation.easeInOut(duration: 1)) {
            viewModel.deal()
        }
        for _ in 1..<numberOfCards {
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.7) {
                withAnimation(.easeInOut) {
                    viewModel.deal()
                }
            }
        }
    }
1
  • Look into creating a custom queue with all the items enqueued up, and then execute the queue after your loop. Or better, since you're using animations, use a keyframe animation? Commented Jun 28, 2020 at 21:28

2 Answers 2

2

Try this

func deal(_ numberOfCards: Int) {
    withAnimation(Animation.easeInOut(duration: 1)) {
        viewModel.deal()
    }
    for i in 1..<numberOfCards {
        DispatchQueue.main.asyncAfter(deadline: .now() + Double(i) * 0.7) {
            withAnimation(.easeInOut) {
                viewModel.deal()
            }
        }
    }
}
Sign up to request clarification or add additional context in comments.

Comments

0

The problem is that you’re starting all of them 0.7 seconds from now. You want to multiply that interval by the for loop index. You can probably also simplify it a bit, e.g.:

func deal(_ numberOfCards: Int) {
    for i in 0..<numberOfCards {
        DispatchQueue.main.asyncAfter(deadline: .now() + Double(i) * 0.7) {
            withAnimation(.easeInOut) {
                viewModel.deal()
            }
        }
    }
}

This pattern isn’t ideal, though, because if you dismiss the view in question, it’s still going to be trying to flip cards on view that isn’t present anymore. Also, this pattern of issuing multiple asyncAfter is not great because it’s subject to timer coalescing (where latter calls may be coalesced together to save battery). This latter issue might not be an issue here, but as a general rule, we use Timer to prevent coalescing and to be able to cancel the timer when the view is dismissed.

func deal(_ numberOfCards: Int) {
    var cardNumber = 0
    let timer = Timer.scheduledTimer(withTimeInterval: 0.7, repeats: true) { timer in
        withAnimation(.easeInOut) {
            viewModel.deal()
        }
        
        cardNumber += 1
        if cardNumber >= numberOfCards {
            timer.invalidate()
        }
    }
    timer.fire()
}

If this was in a class, I might use [weak self] in the timer closure with

guard let self = self else {
    timer.invalidate()
    return
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.