WWDC24 SALE: Save 50% on all my Swift books and bundles! >>

Day 34: Challenge. Solved but I am looking for better solution.

Forums > 100 Days of SwiftUI

Hi everyone,

I am following 100 days of SwiftUI schedule. I have finished day 34 and my code run without error. However to be honest I am not very happy with my code;

  1. I used "newArr" variable array to keep track if the button is tapped or not which I beleive there is more effective solution for that purpose.
  2. I could not find a way to alert message to appear below flag images.
  3. Game is on as round < 5. I used if statement for this purpose but I think using while loop is better for this purpose. However I am not allowed to use while inside struct. Is there any specific reason ?

Please see my code below;


import SwiftUI

struct Title: ViewModifier {

    func body(content: Content) -> some View {
        content
        //            .font(.largeTitle.bold())
            .font(.system(size: 36.0).bold())
            .foregroundColor(.white)
    }
}

extension View {
    func titleStyle() -> some View {
        modifier(Title())
    }
}

struct FlagImage: View {
    var text: String
    var rotateAmount: Double
    var opacityAmount: Double
    var scaleAmount: Double

    var body: some View {
            Image(text)
                .renderingMode(.original)
                .clipShape(Capsule())
                .overlay(Capsule().stroke(Color.black, lineWidth: 2))
                .shadow(radius: 5)
                .opacity(opacityAmount)
                .scaleEffect(scaleAmount)
                .rotation3DEffect(.degrees(Double(rotateAmount)),
                                  axis: (x: 0.0, y: 1.0, z: 0.0))
    }
}

struct ContentView: View {
    @State private var showingScore = false
    @State private var scoreTitle = ""

    @State private var countries = ["Estonia", "France", "Germany", "Ireland", "Italy", "Nigeria", "Poland", "Russia", "Spain", "UK", "US"].shuffled()
    @State private var correctAnswer = Int.random(in:0...2)
    @State private var score = 0
    @State private var round = 0
    @State private var newArr = [false, false, false]  // Used to check if the button is tapped. true if button is tapped.
    @State private var rotateAmount = 0.0
    @State private var opacityAmount = 1.0
    @State private var scaleAmount = 1.0

    var body: some View {
        ZStack {
            RadialGradient(stops: [
                .init(color: Color(red:0.1, green: 0.2, blue: 0.45), location: 0.3),
                .init(color: Color(red:0.76, green: 0.15, blue:0.26), location: 0.3)
            ], center: .top, startRadius: 200, endRadius: 700)
            .ignoresSafeArea()

            VStack {
                Text("Guess the Flag")
                    .titleStyle()
                if round<5 {

                    VStack(spacing: 15) {
                        VStack {

                            Text ("Tap the flag of")
                                .foregroundStyle(.secondary)
                                .font(.subheadline.weight(.heavy))
                            Text(countries[correctAnswer])
                                .font(.largeTitle.weight(.semibold))
                        }
                        ForEach(0..<3) { number in
                            Button {
                                flagTapped(number)
                                newArr[number] = true
                                withAnimation(.easeOut(duration: 1)) {
                                    rotateAmount += 360
                                }
                                withAnimation() {
                                    opacityAmount -= 0.75
                                    scaleAmount -= 0.5
                                }

                            } label: {
                                FlagImage(text: countries[number],
                                          rotateAmount: (newArr[number] ? rotateAmount : 0),
                                          opacityAmount: (!newArr[number] ? opacityAmount : 1),
                                          scaleAmount: (!newArr[number] ? scaleAmount : 1)
                                )
                            }
                        }
                    }
                    .frame(maxWidth: .infinity)
                    .padding(.vertical, 20)
                    .background(.thinMaterial)
                    .clipShape(RoundedRectangle(cornerRadius: 20))

                    Spacer()
                    Spacer()

                    Text("Score: \(score)")
                        .foregroundColor(.white)
                        .font(.title.bold())

                    Spacer()
                } else {
                    VStack(spacing: 15) {
                        VStack {
                            Spacer()
                            Text ("Game is over. Your score is \(score)/8")
                                .foregroundStyle(.secondary)
                                .font(.largeTitle.weight(.semibold))

                            Spacer()
                        }
                        Button {
                            restart()
                        } label: {
                            Text("Click to restart the game!")
                                .padding()
                                .foregroundColor(.white)

                        }
                        .buttonStyle(.borderedProminent)
                        .tint(.mint)                        
                    }
                }
            }
            .padding()
        }
        .alert(scoreTitle, isPresented: $showingScore) {
            Button("Continue", action: askQuestion)
        } message: {
            Text("Your score is \(score)")
        }
    }

    func flagTapped(_ number: Int) {
        if number == correctAnswer {
            scoreTitle = "Correct"
            score += 1
        } else {
            scoreTitle = "Wrong. That is the flag of \(countries[number])"
        }
        round += 1
        showingScore = true
    }

    func askQuestion() {
        countries.shuffle()
        correctAnswer = Int.random(in: 0...2)
        newArr = [false, false, false]
        opacityAmount = 1.0
        rotateAmount = 0.0
        scaleAmount = 1.0

    }

    func restart () {
        score = 0
        round = 0
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Thanks in advance.

2      

One thing I would ask is why you are using a whole array newArr when you only need a single number. You already have a function flagTapped that checks if the chose flag is the correct one, and does that by comparing just a single value.

Similarily, You could replace newArr, with just a single state variable integer, and modify that as needed when a flag is tapped or a new question is asked. Then use that new variable in your animation selection.

@State private var chosenFlag = -1
FlagImage(text: countries[number],
          rotateAmount: (chosenFlag == number ? rotateAmount : 0),
          opacityAmount: (chosenFlag == number ? 1 : opacityAmount),
          scaleAmount: (chosenFlag == number ? 1 : scaleAmount)

2      

Thanks, that's solved my problem.

I still do not know how to move alert message to below ..

2      

Save 50% in my WWDC sale.

SAVE 50% To celebrate WWDC24, all our books and bundles are half price, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.

Save 50% on all our books and bundles!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.