UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

Day 22 - Guess the Flag(multiple questions incl. wrapUp task)

Forums > 100 Days of SwiftUI

Hi people

Have been asked by @Obelix to create a separate topic. Done

Need some help/advice. The code:

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

    @State private var gameScore = 0
    @State private var selectedImage = ""
    @State private var reachingQuestionLimit = false
    @State private var guessFlagQuestionCount = 0

    @State private var countries = ["Estonia", "France", "Germany", "Ireland", "Italy", "Nigeria", "Poland", "Russia", "Spain", "UK", "US"].shuffled()

    @State private var correctAnswer = Int.random(in: 0...3)

    var body: some View {
        ZStack {
            // ZStack lets us layer views on top of each other.
            // It's about depth

            RadialGradient(
                stops: [
                .init(color: Color(red: 0.12, green: 0.15, blue: 0.25), location: 0.3),
                .init(color: Color(red: 0.76, green: 0.15, blue: 0.26), location: 0.67),
                .init(color: Color(red: 0.3, green: 0.1, blue: 0.2), location: 0.5)
                ], center: .top, startRadius: 200, endRadius: 900)

                .ignoresSafeArea()

            VStack {
                Spacer()

                Text("Guess the flag")
                    .font(.title.weight(.thin))
                    // .font(.largeTitle.bold()) - shortcut
                    .foregroundColor(.white)

                VStack(spacing: 30) {
                    VStack {
                        Text("Tap the flag of")
                            .foregroundStyle(.secondary)
                            .font(.subheadline.weight(.semibold))

                        Text(countries[correctAnswer])
                            // .foregroundColor(.white) - we take it off to have
                            // a change after switching from light to dark mode
                            .font(.largeTitle.weight(.semibold))
                    }

                    ForEach(0..<4) { number in
                        Button(role: .destructive) {
                            flagTapped(number, imageName: countries[number])
                        } label: {
                            Image(countries[number])
                                .renderingMode(.original)
                                .clipShape(RoundedRectangle(cornerRadius: 8))
                                //.clipShape(Capsule()) Pauls option in tutorial
                                // this is the shape of buttons (images in our case)
                                .shadow(radius: 10)
                        }
                    }
                }
                .frame(maxWidth: .infinity)
                .padding(.vertical, 25)
                .background(.ultraThinMaterial)
                .clipShape(RoundedRectangle(cornerRadius: 20))

                Spacer()
                Spacer()

                // Spacer()
                Text("Your score: \(gameScore)")
                    .foregroundColor(.white)
                    .font(.body.weight(.regular))

                Spacer()
            }
            .padding()
        }

        .alert(scoreTitle, isPresented: $showingScore) {
            Button("Continue", action: continueGame)
        } message: {
            if scoreTitle == "Correct!" {
                Text("Your score is: \(gameScore)")
            }
            if scoreTitle == "You've missed!" {
                Text("""
                    This is actually the flag of \(selectedImage)
                    Your score is now: \(gameScore)
                    """)
            }
        }
        .alert("Round finished", isPresented: $reachingQuestionLimit) {
            Button("New game", action: reachLimit)
            Button("Quit", action: reachLimit)
        } message: {
            Text("""
                Final score: \(gameScore)
                Let's give it another try?
                """)
        }

    }

    func flagTapped(_ number: Int, imageName: String) {
        if number == correctAnswer {
            scoreTitle = "Correct!"
            gameScore += 1
            guessFlagQuestionCount += 1

        } else {
            scoreTitle = "You've missed!"
            gameScore -= 1
            guessFlagQuestionCount += 1
        }

        selectedImage = imageName

        if guessFlagQuestionCount == 5 {
            showingScore = true
            reachingQuestionLimit = true
        }
        showingScore = true
        }

    func continueGame() {
        countries.shuffle()
        correctAnswer = Int.random(in: 1...3)
    }

    func reachLimit() {

        continueGame()
        gameScore = 0
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
  1. When someone chooses the wrong flag, tell them their mistake in your alert message – something like “Wrong! That’s the flag of France,” for example.

I made this thing a little different than other options I've seen here on forum. Probably everyone used the easier option to get the name of the country in the TITLE, not in the note of the Alert. IMO it was not quite correct from UX/UI point of view. So I tied the output with Images. Seems to work ok.

  1. Make the game show only 8 questions, at which point they see a final alert judging their score and can restart the game.

Somehow I managed to make this based on the attempt of @CodeMaverickDev . It works But I have a question regarding the final implementation.

First of all my intention was to get the alert about "Correct" or "Wrong" pick even after making the last tap of the "Round". I think it should be like that. And only after that to get the second Alert about "Round is finished. Start again?"
So I made it work but I have a bug. I start a game, make my 5 attempts, and sometimes I GET my alert about "Correct" or "Wrong" pick, but sometimes I DON'T. What is the reason if I wrote the code to show both of alerts ? Sometimes also I get the "Correct" or "Wrong" alert already after "Restart' which shouldnt be like that either.

 if guessFlagQuestionCount == 5 {
            showingScore = true
            reachingQuestionLimit = true

view 1 view 1

p.s. sorry, looks like I'm an idiot cause I cant figure out how to insert Images :(

Second problem is: After we finish the Round and choose to start again I go through same 5 taps and It's supposed that the cicle will continue. I mean again the alerts and another Round or "Quit" But on the second Round I do not get the "Restart" alert anymore. Score doesn't reset to 0 and I just continue to tap on flags and receive only "Correct" or "Wrong" Allert. That's a clear bug which I'd like to fix.

I know It's not necessary now at this point of studies but I'm a perfectionist and I'd like to know how to fix that. Even thought it's a simple "Kindergarden" level app, it should look and work properly.

Thanks for your replies, people !

p.s. small purple alert from xCode here: "Gradient stop locations must be ordered."

   RadialGradient(
                stops: [
                .init(color: Color(red: 0.12, green: 0.15, blue: 0.25), location: 0.3),
                .init(color: Color(red: 0.76, green: 0.15, blue: 0.26), location: 0.67),
                .init(color: Color(red: 0.3, green: 0.1, blue: 0.2), location: 0.5)
                ], center: .top, startRadius: 200, endRadius: 900)

                .ignoresSafeArea()

Why do I get it ? It does not affect/blocks previews like Red alerts but still it's kinda annoying

p.p.s I tried to make a small update making the different button for the end of Round Alert. I would like to see the text being red for "Quit" for example. I tried to use ".destructive" but after that I have already 3 options : New game, Quit and Cancel. Why it happens ? And how to onle make "Quit" part being "Red" ?

2      

re: The warning about gradient stops...

You should specify gradient stop locations in increasing order.

2      

This is a simplified version of a game. But watch the internal state! As the player makes guesses, the currentRound is updated. This triggers a closure to check if the currentRound is past the number of rounds.

In a declarative language, you DECLARE when you want to see the Game Over alert.

When you have multiple states in your application, I recommend you add a temporary view showing your @State variables. Then you can see what the state is after each button press. You'll get the opportunity to evaluate the state of your application and make a decision about what BUTTONS should be displayed, what COLOR each field should be, what TEXT should be displayed.

As you run this application, watch the state variables. Then as they change, evaluate how the changing state affects the alerts, text, and other view parameters.

//  GameView.swift
//  Created by Obelix on 9/3/22.

import SwiftUI
// Simple game. Objective: Get a high score!
// Please don't use ContentView. Give your view a descriptive name!
struct GameView: View {
    @State private var score          =  0      // Start game with zero space credits
    private var        rounds         = 15      // How many rounds in your game?
    @State private var currentRound   =  1      // Player's current state
    @State private var gameOver       = false   // Calculate this when needed
    private var last3Rounds : Bool { currentRound > rounds - 3 }

    var body: some View {
        VStack(spacing: 40){
            // This is temporary view to show program state.
            VStack(spacing: 10) {
                Text(gameOver ? "Game Over" : "Keep Playing")
                Text("Score: \(score)").padding()
                Text("Round: \(currentRound)").foregroundColor(last3Rounds ? .red : .blue)
                Text("Last 3 Rounds: \(last3Rounds ? "True" : "False")" )
            }.padding().background { Color.indigo.opacity(0.4) }
            Text("Objective: \nGet the highest score!").font(.title3).foregroundColor(.indigo)
            HStack(spacing: 30) {
                // SwiftUI is declarative!  DECLARE what you want to see.
                TapFlagButton(score: $score, round: $currentRound, action: .increment)
                TapFlagButton(score: $score, round: $currentRound, action: .decrement)
            }.font(.largeTitle.bold())
        }
        .onChange(of: currentRound ) { _ in
            gameOver = currentRound > rounds // Change the game state, if necessary
        }
        .alert("Game Over", isPresented: $gameOver) { // DECLARE your intent. Show this WHEN gameOver is true
            Button("Ok", action: newGame )
        } message: {
            Text("""
                Final score: \(score)
                Again?
                """)
        }
    }
    // Keep it simple. You're not changing look and feel stuff here. Just the state.
    func newGame() {
        currentRound = 1
        score        = 0
    }
}

// This is a single Lego piece. It contains its own logic.
struct TapFlagButton: View {
    @Binding var score: Int               // game score
    @Binding var round: Int               // which round?
    var action: ScoreAction = .increment  // can be changed when initialized
    var increaseScore: Bool { action == .increment }  // calculate when needed

    var body: some View {
        Button { // Button action. Change score. Update round. Don't go crazy, keep is simple
            score  = score + (increaseScore ? 1 : -1 )  // add or subtract 1
            round += 1                                  // always increment the round when tapped.
        } label: {
            Image(systemName: increaseScore ? "plus" : "minus")
        }
    }
}

enum ScoreAction { case increment, decrement } // only two scoring options

2      

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

re: The warning about gradient stops...

You should specify gradient stop locations in increasing order.

Thanks for reply, @roosterboy . Although as I thought changing the order of my gradients is changing the way my Background View looks. So if I want it to be exactly like I want it to look, there will be no way to avoid that warning, correct ? If yes then not a big deal for me. But it was nice to know other possible options.

2      

Thanks @Obelix .Took me some time to get into things you have written about.

I will try to go step by step with all that:

  1. In a declarative language, you DECLARE when you want to see the Game Over alert.

I've seen this stetement from you previously so went to check in Google what that means. And I didn't get a clear answer cause Wiki says Swift is both Declarative and Imperative type language. Moreover Swift is not clearly mentioned anywhere in a list of prominent examples of neither Declarative nor Imperative languages.
So your statement is already confusing for me as a person who has complete 0 experience or knowledge in programming before I started Paul's 100DaysofSwiftUI 2 months ago. And now I'm only on day 24. Besides that I'm not a native English speaker which makes it even more difficult to understand everything I'm going through now.

Anyway I will spend more time googling to understand what you mean by saying: "Declare".

  1. // Please don't use ContentView. Give your view a descriptive name! struct GameView: View {

I didn't know we can/are allowed to change it to what we want. But anyway, after I entered your code, I get the error in preview part of code. So I changed default "ContentView" to your "GameView" but after that preview window still doesn't work. And only after changing it here seems like preview started working.

@main
struct obelix_exampleApp: App {
    var body: some Scene {
        WindowGroup {
            GameView()
        }
    }
}

So should I manually type MyNameView everytime I create something new or is there any other place I can set it manually so it will change automatically?

  1. "I recommend you add a temporary view showing your @State variables. Then you can see what the state is after each button press."
  • you mean the "temporary view" is your VStack with code / the indigo rectangle with depicted @State variables ?
  1. "You'll get the opportunity to evaluate the state of your application and make a decision about what BUTTONS should be displayed, what COLOR each field should be, what TEXT should be displayed."

I assume that suppose to be the answer to my question about the way buttons look(.destructive). The only difference you don't say: "Do this here, do that there and then you will get what you need". On one hand I would get direct explanation how to achieve the result. On the other - maybe it's too easy and your option is the better way to teach someone. Still. I dont know, maybe it's a great reply to someone with more knowledge in programming than I have (which is almost 0), but I will try to understand how to implement your advice in practice.

  1. "As you run this application, watch the state variables. Then as they change, evaluate how the changing state affects the alerts, text, and other view parameters."

Are you talking about "the changes in state variables" in the "indigo rectangle area" while I run the game in Simulator ? Or there's the other place in Xcode where I can see those changes in code in text format ? Maybe a particular separate window in a program ?

  1. Code
    struct TapFlagButton: View {
    @Binding var score: Int               // game score
    @Binding var round: Int               // which round?
    var action: ScoreAction = .increment  // can be changed when initialized
    var increaseScore: Bool { action == .increment }  // calculate when needed
  1. clearly new thing to me to create a separate struct for Buttons :O
  2. ummm, what is "@Binding var" - ? again, I swear there was nothing about this thing before day 24
  3. .increment and .decrement supposed to be Swifts internal commands ? cause I cant see them anywhere in your code being set as var/let manually.

Even though I understood the general flow of your code and the mechanism of how it all works to produce the final result in Simulator, I wouldn't be able to write all that now by myself in the way you did.

"// Keep it simple."

it's a great advice. Again, if I understand correct, where you're pointing is: "do not try to overcomplicate things atm with the visual part. all the colors and fonts and gradients, etc. Try to understand the system of how it works and why. THE LOOK can be updated later. general Functionality matters first of all." I'm an architect by education, so maybe that's why I pay so much attention to details about perfection of Visual Look of all I'm creating now.
I need to get the system. And that's where I struggle and sure will have much pain in future if I won't give up.

Simple things to you, sir, are almost "nuclear physics" for me now :) (cause as I understand from many of your comments on forum you either have some experience in programming or a gifted man to understand programming) Not even mentioning about individual capabilities of learning and understanding the particular types of information.


I'm not gonna lie, programming is quite difficult for me. Sometimes I read 1-2 but sometimes 5 times or 10 times to get into something other guy would understand from first time and it would be still obvious for him why others struggle to understand that. But I don't want to give up. I'm sure I'm not so stupid not to get it. Maybe I won't become best Swift dev in a world but I'm sure I can definitely achieve some success in this craft. If one man can do that - other man can do that too.

But I definitely need more answers "facepalm"

Appologies for "longreed" and not understanding "obvious" things.

2      

"I recommend you add a temporary view showing your @State variables.
Then you can see what the state is after each button press."
you mean the "temporary view" is your VStack with code /
the indigo rectangle with depicted @State variables ?

Yes! This helps you determine your game's state after one action, and before the user presses another button. Use this view only while developing your app.

Your state is:

  1. Round: 13
  2. Score: 9
  3. Game Over: FALSE
  4. Last 3 rounds: TRUE <-- What do I want to show when user is playing last 3 rounds?

You can SEE the state by using a temporary View in your code. Then you can think through the design. For example, in my code when the user is in the last 3 rounds (TRUE) I decided I wanted the Round label to turn red. I DECLARED this label will be RED when the last3Rounds variable is true.

Likewise, you can determine what happens when the Score is over 10, or what sad face emoji you show if the score is below 5. You make these decisions based on the program state. Then you declare what you want to show.

Add this computed var to your GameView struct:

// Return a string based on the currentRound and score
    private var scoreEmoji : String {
        if currentRound > 10 {
            if score > 9 { return "🥳" }
            else if score > 7 { return "😛"}
            else if score > 4 { return "😐"}
            else if score > 2 { return "😵"}
            else { return "🤢"}
        }
        return "🫥"
    }

How do you get this new scoreEmoji into your view? Compare these two approaches:

// Declarative --------------------------------
// Tell SwiftUI what you want to see. This is the Swifty way!
Text( scoreEmoji ).font(.largeTitle)

// Imperative -----------------------------
// Generate different views based on game scores.
// Don't clutter your view code with game logic.
if currentRound > 10 {
    if score > 9 { Text("🥳") }
    else if score > 7 { Text("😛") }
    else if score > 4 { Text("😐") }
    else if score > 2 { Text("😵") }
    else { Text("🤢") }
}

Are you talking about "the changes in state variables" in the "indigo rectangle area" while I run the game in Simulator ?

Yes!
Indigo rectangle area = Your car's dashboard. It shows the program's important variables (fuel state, speed, headlamps, etc)
The state dashboard probably is just a temporary view while you're building your application.

Or there's the other place in Xcode where I can see those changes in code ?

No!

2      

cause as I understand from many of your comments on forum you either
have some experience in programming or
a gifted man to understand programming

I remember the pains I faced while learning programming. This is why I continue to contribute to this community. I try a teach-by-example to help others fill in the gaps.

You're on Day 21? 24? Take a day, then return to day 12 and repeat. You'll pick up more, and the knowledge will come.

To me programming is mostly solving puzzles. I'm not gifted in programming. I just like solving puzzles! With SwiftUI, the puzzle pieces are Views, Pickers, TextFields, etc. After a while, you start to see how some pieces look like they SHOULD fit, but don't. Then you remember some other pieces, and voila! You find an elegant way to solve your programming puzzle.

Keep coding!

2      

ummm, what is "@Binding var" - ? again, I swear there was nothing about this thing before day 24

Yikes! My fault.

Bindings come in later lessons. Here's an example where I used a better "puzzle piece" to solve a problem.

Sorry mate. You'll get to this soon enough.

2      

clearly new thing to me to create a separate struct for Buttons :O

Ok! So this is a great lesson for you. Don't think about this as a separate struct for a Button. TapFlagButton takes a few parameters and returns a View puzzle piece. In other forum posts, I also use the term Lego piece. A struct that returns a view is just a small chunk of code that happens to draw something to the screen, even interact with the user. You can reuse this Lego piece whereever needed in your application. Reuse = Great Programming.

Instead, consider this a separate struct for a View part! Headers are views, footers are views. Dashboards are views. Menu items are views.

You'll start to look at applications on your iPhone in a different way! You'll come to understand that apps on your iPhone are just a collection of dozens of Views (reuseable Lego bricks). Then, as a SwiftUI programmer, you'll start to think about HOW you can create those views?

Rather than trying to create one "ContentView" that holds your entire application, consider creating a "GameView" that holds a dozen smaller views.

If you want some homework, here's a reading assignment:

See -> Building Lego Bricks in SwiftUI

2      

@rooster noted the gradients should be in increasing location order.

Not in location order:

.init(color: Color(red: 0.12, green: 0.15, blue: 0.25), location: 0.3),   // 01
.init(color: Color(red: 0.76, green: 0.15, blue: 0.26), location: 0.67),  // 03
.init(color: Color(red: 0.3,  green: 0.1,  blue: 0.2),  location: 0.5)    // 02

Same, but in increasing location order:

.init(color: Color(red: 0.12, green: 0.15, blue: 0.25), location: 0.3),   // 01
.init(color: Color(red: 0.3,  green: 0.1,  blue: 0.2),  location: 0.5),   // 02
.init(color: Color(red: 0.76, green: 0.15, blue: 0.26), location: 0.67)   // 03

2      

First of all thanks a lot for spending your time to school other students here, Obelix! Not many people want to give their free fime for help and advices which won't give them back anything. That deserves huge respect !

Now as usual - step by step:

  1. "@rooster noted the gradients should be in increasing location order."

Did exactly that. But as I said - it changes the way gradient looks. So if I'd like to keep my "strange/wrong" look, I got to keep up with the violet note. Otherwise "increasing" order makes "proper,smooth" gradient.

  1. "You're on Day 21? 24? Take a day, then return to day 12 and repeat. You'll pick up more, and the knowledge will come."

Day 24 now. Doing the wrapUp and I'll write more questions regarding that little later in this thread cause task 2 and 3 are related to FlagApp. I will come back to day 10 and 12 for sure as you suggested. It's never a bad thing to repeat the fundamentals !

"To me programming is mostly solving puzzles. I'm not gifted in programming. I just like solving puzzles!"

Puzzles are my weak point. That's one of the reasons I decided to get into programming. To become better in problem solving things. And I really hope my dedication and patience will give me a chance to reach success in this.

3."Bindings come in later lessons. Here's an example where I used a better "puzzle piece" to solve a problem."

No issues. I'm glad it was you to run a bit ahead in your lesson and not me to miss something that has been taught before day 24. Means my attention is still working he-he.

2      

4. "Yes! This helps you determine your game's state after one action, and before the user presses another button. Use this view only while developing your app... ...You can SEE the state by using a temporary View in your code. Then you can think through the design."

You mean you create some kind of a template at the beginning to make it easier to produce final result ? But then what you do with that temporary View, you just delete that code/ put it in "//" notes ? or simply create a new project file where you create a "final product" based on the template ?

5. "Add this computed var (emoji) to your GameView struct:"

Ok, I'll do that. I think I got the EMOJI exapmle about Declaration. Question about your GameView struct to come in below.

6. "Don't think about this as a separate struct for a Button... Instead, consider this a separate struct for a View part! Headers are views, footers are views. Dashboards are views. Menu items are views. "

Wow. That statement definitely needs some time to settle in my head. I couldn't figure out that by myself. Sound's like a general concept of what SwiftUI actually is and how we need to see/understand it.

7. "Rather than trying to create one "ContentView" that holds your entire application, consider creating a "GameView" that holds a dozen smaller views."

This is an important thing. Can you stop here for a moment and add more thoughts? Cause I still don't think I understand.

"ContentView" is a default struct while creating a project. Paul is teaching us to work inside this struct (at least until day24). You say - don't use "ContentView", make your own struct "GameView" and work inside of it. But:

  1. How you create it? You just manually delete the text in code field and change "Content" to "Game" in few places and rename the "ContentView.swift" under "obelix_exampleApp.swift": 1 2 3

Or you create it paralel to struct "ContentView" so they both exist inside the "ContentView.swift" main window where we work and write our magic in Xcode ?

  1. Most important. Why do you create it, this personal custom struct "GameView" ?
    Why can't you write all the same code you show me inside the default struct "ContentView" ?

If Paul will teach about this in later lessons of 100Days then I will get the answer. But If not, and you came to this by your own - then I'm curious to know why. And what benefits it gives us.

Or Maybe you can share a link to an article/youtube video where it will be nicely explained.

I hope I didn't forget anything "facepalm" : )

2      

Forgot to mention that a great lesson in your educative piece of code was the use of Ternary operator. I noticed and remembered how Paul was saying about power of that thing and it's wide use in SwiftUI.

It was extremely pleasant to see the example of that statement in your code! How well you do it if there is a chance to use it.

Text("Round: \(currentRound)").foregroundColor(last3Rounds ? .red : .blue)
Text("Last 3 Rounds: \(last3Rounds ? "True" : "False")" )

score  = score + (increaseScore ? 1 : -1 )
Image(systemName: increaseScore ? "plus" : "minus")

Plus as I understand, that is the example of use of computed property ? :

private var last3Rounds : Bool { currentRound > rounds - 3 } 

3      

Forgot to mention that a great lesson in your educative piece of code was the use of Ternary operator.
I noticed and remembered how Paul was saying about power of that thing and it's wide use in SwiftUI.

It took a while before the ternary became part of my tool box. Instead I'd use lots of if statements, etc.

I like to think of the ternary operator as a light switch in my application. It receives a value that's either ON or OFF. So in my application, if a variable is ON, maybe I'll make the text .blue. If it's OFF, I'll make it bold and .red.

Again, this is the declarative nature of SwiftUI. Tell the compiler: I declare this text to be blue when gameOver variable is true, otherwise I declare this variable is red.

3      

"It took a while before the ternary became part of my tool box. Instead I'd use lots of if statements, etc. I like to think of the ternary operator as a light switch in my application."

I think it's common for most of learners to naturally use "if/else" at the beginnings cause they get most of attention in any resourse about "how to learn insert language/coding". And only with time, as you grow, you switch to something which is more simple, useful and clean looking. Of course if you want go grow and become more efficient :) Tnanks again for your wisdom, @Obelix. Your contribution is huge.

p.s. Hope you will find time to respond on the rest of my questions above.

2      

@rooster noted the gradients should be in increasing location order.

Did exactly that. But as I said - it changes the way gradient looks. So if I'd like to keep my "strange/wrong" look, I got to keep up with the violet note. Otherwise "increasing" order makes "proper,smooth" gradient.

You can always use something like this for your background:

ZStack {
    Color(red: 0.3, green: 0.1, blue: 0.2)
    Circle()
        .fill(RadialGradient(
            stops: [
                .init(color: Color(red: 0.12, green: 0.15, blue: 0.25), location: 0.3),
                .init(color: Color(red: 0.12, green: 0.15, blue: 0.25), location: 0.75),
                .init(color: Color(red: 0.76, green: 0.15, blue: 0.26), location: 0.99),
            ], center: .top, startRadius: 200, endRadius: 900))
        .frame(width: 980, height: 980)
        .offset(y: -(980 / 4))
}

Which one's which? two gradients

They're not 100% identical but can always be tweaked more.

3      

"Which one's which?"

@roosterboy, pretty sure mine is on left side. Am I right ?

Sure, I'm not saying it was impossible to make a dupe with propper written code. Of course it's possible, just requires some time to play with and knowledge of instruments (which I definitely do not know well atm). And I'm shaking your hand for taking time trying to show me that this is possible. Seriously, thank you, sir !

Although if insert your piece of code into my I somehow get the different looking frame. And if I try to use ".frame(maxWidth: .infinity)" it "crashes". Where do I make mistake ?

1 2

2      

Yurii should re-read the article on Lego Bricks linked above! After that it's tIme to learn some debugging techniques!

First, look at your complex view. Can you simplify your view?
Can you break your complex view into smaller Lego bricks? Yes!

struct GradientTest: View {
    var body: some View {
        ZStack {
            ColorBackground(ignoresSafeArea: false) // <-- Try false, then again as true
            // GradientCircle() // <-- Factor out into its own view. Then uncomment
        }
    }
}

// Break your complex view into smaller Lego bricks!
struct ColorBackground: View {
    var ignoresSafeArea            = false
    let backgroundColor: some View = Color(red: 0.3, green: 0.1, blue: 0.2)
    var body: some View {
        // ternary operator again!  Second option = Stay out of safe areas
        ignoresSafeArea ? backgroundColor.ignoresSafeArea() : backgroundColor.ignoresSafeArea(edges: [])
    }
}

// Here's another Lego brick. Not part of your question to rooster.
struct GradientCircle: View {
    var body: some View {
        let startRadius = 200.0  // CGFloat
        let endRadius   = 900.0
        let gradientStops: [Gradient.Stop] = [
            .init(color: Color(red: 0.12, green: 0.15, blue: 0.25), location: 0.3),
            .init(color: Color(red: 0.12, green: 0.15, blue: 0.25), location: 0.75),
            .init(color: Color(red: 0.76, green: 0.15, blue: 0.26), location: 0.99)
        ]
        // ------------------ Custom Circle View -----------------
        Circle()
            .fill(RadialGradient( stops:       gradientStops,
                                  center:      .top,
                                  startRadius: startRadius,
                                  endRadius:   endRadius))
            .frame(width: 980, height: 980).offset(y: -(980 / 4))
    }
}

2      

"Yurri should re-read the article on Lego Bricks linked above! After that it's tIme to learn some debugging techniques!"

Hi there. I must apologize for delay. I'm from Ukraine so number 1 goal here right now is "to survive" + to manage other things to maintain existance. That means sometimes Swift needs to wait a little.

I only managed to read your article about Lego-bricks day ago and I understand your idea. That is a clever and efficient way to simplify the process of creating a code and the way how it looks. + it's the possibility to use/reuse those "bricks" in other projects so you don't need to write tons of code again and again form scratch. I understand that this is SMART WAY to do like you offer. But YOU NEED TO KNOW HOW TO USE THIS "techniqe" to make everything work well alltogether if that makes sense.

I see what you want to give me. But I don't understand how to "use" this knowledge. How to implement it in a perfect way.

"First, look at your complex view. Can you simplify your view? Can you break your complex view into smaller Lego bricks? Yes!"

Sure It's possible, we can simplify it. You did that.
I see how your code looks and it's way more appealing to my eye. I understand that it's better option. But I can't make it work I insert your code into mine and everything goes sh#t. Why ? Even though I keep all my variables on their place and I just deleted the part with ZStack and gradient manipulations.

at the same time I managed to build your puzzles in Playgroung and see the result. Everything went well there

img

Don't get me wrong, I'm extremely thankful for your help that you want to give. But it's either something missing (for me) in your explanation or it's too soon for me to adopt it at day 25. Or maybe I'm dumb. Which is also the possible scenario. So feel free to tell me if I'm not suitable for programming as a thing.

I've been asking you a question about how and where you create those "custom" View structs.
Do they replace the struct ContentView: View { or they exist parallel and we're just keeping default struct ContentView: View { emply while creating our own Lego-brick structs. Or we delete struct ContentView: View { from our code cause we don't need it anymore. etc

I know it's already obvious for you, but not for me. That's why I cant make it work yet the way you do.

p.s. I know slavic names are difficult to pronounce :) but one little thing if you don't mind: Yurii (Yuriy/ Iurii/ Jurij ) but not yuRRi. no problems though about this mistake. all good

2      

Meanwile I'll post my full current code again.

At day 24 wrapUp there was tasks 2 and 3 to implement in tapFlag.app I did task 3 and created a custom struct + extension for the title with modificators. That wasn't difficult, just follow Pauls example. But I failed with task 2.

As I understand we need to make one of our flags look different then others having it's own modifiers. Let's say button n.3 out of 4. I achieved the visual look eventually (yeah, manually by making a separate "button" out of ForEach) but not in the way we were supposed to do that in the task.

So, can anyone please explain how to do that ?

cause I didn't find the solution in other topics here on forum. (Am I only one so stupid not being able to do that?) Thanks people !

import SwiftUI

// remember how to write those custom modifiers !
struct LargeTiltle: ViewModifier {
    func body(content: Content) -> some View {
        content
            .font(.title.weight(.medium))
            .foregroundColor(.white)
    }
}
extension View {
    func largeTitleStyle() -> some View {
        modifier(LargeTiltle())
    }
}

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

    @State private var gameScore = 0
    @State private var selectedImage = ""
    @State private var reachingQuestionLimit = false
    @State private var guessFlagQuestionCount = 0

    @State private var countries = ["Estonia", "France", "Germany", "Ireland", "Italy",
                                    "Nigeria", "Poland", "Russia", "Spain", "UK", "US"].shuffled()

    @State private var correctAnswer = Int.random(in: 0...3)

    var body: some View {
        ZStack {
            // ZStack lets us layer views on top of each other.
            // It's about depth

            RadialGradient(
                stops: [
                .init(color: Color(red: 0.12, green: 0.15, blue: 0.25), location: 0.3),
                .init(color: Color(red: 0.76, green: 0.15, blue: 0.26), location: 0.67),
                .init(color: Color(red: 0.3, green: 0.1, blue: 0.2), location: 0.67)
                ], center: .top, startRadius: 200, endRadius: 900)
                .ignoresSafeArea()

            VStack {
                Spacer()
                Spacer()

                    Text("Guess the flag")
                        .largeTitleStyle()
                        // here I added custom title modifier from new struct
                        //.font(.title.weight(.thin))
                        // .font(.largeTitle.bold()) - shortcut
                        //.foregroundColor(.white)

                VStack(spacing: 30) {
                    VStack {
                        Text("Tap the flag of")
                            .foregroundStyle(.secondary)
                            .font(.subheadline.weight(.semibold))

                        Text(countries[correctAnswer])
                            // .foregroundColor(.white) - we take it off to have
                            // a change after switching from light to dark mode
                            .font(.largeTitle.weight(.semibold))
                    }

                        Button {
                            flagTapped(3, imageName: countries[3])
                        } label: {
                            Image(countries[3])
                                .renderingMode(.original)
                                .clipShape(Capsule())
                                .shadow(radius: 20)
                        }

                    ForEach(0..<3) { number in
                        Button {
                            flagTapped(number, imageName: countries[number])
                        } label: {
                            Image(countries[number])
                                .renderingMode(.original)
                                .clipShape(RoundedRectangle(cornerRadius: 8))
                                //.clipShape(Capsule()) Pauls option in tutorial
                                // this is the shape of buttons (images in our case)
                                .shadow(radius: 10)
                        }
                    }
                }
                .frame(maxWidth: .infinity)
                .padding(.vertical, 25)
                .background(.ultraThinMaterial)
                .clipShape(RoundedRectangle(cornerRadius: 20))

                Spacer()
                //Spacer()
                //Spacer()
                Text("Your score: \(gameScore)")
                    .foregroundColor(.white)
                    .font(.body.weight(.regular))

                Spacer()
            }
            .padding()
        }

        .alert(scoreTitle, isPresented: $showingScore) {
            Button("Continue", action: continueGame)
        } message: {
            if scoreTitle == "Correct!" {
                Text("Your score is: \(gameScore)")
            }
            if scoreTitle == "You've missed!" {
                Text("""
                    This is actually the flag of \(selectedImage)
                    Your score is now: \(gameScore)
                    """)
            }
        }
        .alert("Round finished", isPresented: $reachingQuestionLimit) {
            Button("New game", action: reachLimit)
            Button("Quit", action: reachLimit)
        } message: {
            Text("""
                Final score: \(gameScore)
                Let's give it another try?
                """)
        }
    }

    func flagTapped(_ number: Int, imageName: String) {
        if number == correctAnswer {
            scoreTitle = "Correct!"
            gameScore += 1
            guessFlagQuestionCount += 1

        } else {
            scoreTitle = "You've missed!"
            gameScore -= 1
            guessFlagQuestionCount += 1
        }

        selectedImage = imageName

        if guessFlagQuestionCount == 5 {
            showingScore = true
            reachingQuestionLimit = true
        }
        showingScore = true
    }

    func continueGame() {
            countries.shuffle()
            correctAnswer = Int.random(in: 1...3)
        }
    func reachLimit() {
        continueGame()
        gameScore = 0
    }
}

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

2      

@tomn  

Here's my attempt for "Guess the Flag" Day 22.

Wins

  • Learned gradients and the awesome styling possibilities.
  • Alerts and toggling the states
  • keeping track of score and question counter

Fails or future tweak attempts

  • Tried to use a computed property for question counter but couldn't quite get it. Just resorted to a simple incrementer in one of the functions.
  • Would have liked to add functionality to make sure the same flag isn't asked twice in a row, or twice in a game of 8.
  • Would have liked to show a question and answer tally score sheet at the end. But that would have required invoking another view, and that hasn't been covered yet.

Screen recording GIF below the code.

struct ContentView: View {
    // array of countries which we have flag image assets for
    @State private var countries = ["Estonia", "France", "Germany", "Ireland", "Italy", "Monaco", "Nigeria", "Poland", "Russia", "Spain", "UK", "US"].shuffled()
    // random integer between 0 and 2 to pick one of the countries
    @State private var correctAnswer = Int.random(in: 0...2)

    // showingScore shows score alert. gameOver shows final score and game reset alert.
    @State private var showingScore = false
    @State private var gameOver = false

    // goes in the title of score alerts.
    @State private var scoreTitle = ""

    // keeps track of user score. Reset to 0 for new game.
    @State private var userScore = 0

    // question counter, keep track of progress. Reset to 0 for new game.
    @State private var currentQuestion = 1

    var body: some View {

        ZStack {
            // used a nice wallpaper image as background
            Image("wallpaper")
                .resizable()
                .ignoresSafeArea()
            VStack {
                Spacer()
                Text("Guess the Flag")
                    .font(.largeTitle.bold())
                    .foregroundColor(.white)

                VStack(spacing: 15) {
                    VStack {
                        Text("Tap the flag of")
                            .foregroundStyle(.secondary).font(.subheadline.weight(.semibold))
                        Text(countries[correctAnswer]).font(.largeTitle.weight(.semibold))
                    }
                    ForEach(0..<3) { number in
                        Button {
                            // flag was tapped
                            flagTapped(number)

                        } label: {
                            Image(countries[number])
                                .renderingMode(.original)
                                .clipShape(Capsule())
                                .shadow(radius: 5)
                        }

                    }

                }
                .frame(maxWidth: .infinity)
                .padding(.vertical, 20)
                .background(.regularMaterial)
                .clipShape(RoundedRectangle(cornerRadius: 20))
                Spacer()
                    // not sure if these alerts could be placed anywhere
                    // but here they are...
                    // Score Alert box
                    .alert(scoreTitle, isPresented: $showingScore) {
                        Button("Continue", action: askQuestion)
                    } message: {
                        Text("Your score is \(userScore)")
                    }

                    // Game over alert box
                    .alert("Game over", isPresented: $gameOver) {
                        Button("Reset game", action: resetGame)
                    } message: {
                        Text("Your score is \(userScore)")
                    }

                Spacer()
                Text("Score: \(userScore)")
                    .foregroundColor(.white)
                    .font(.title.bold())
                Text("Questions: \(currentQuestion)/8")
                    .foregroundColor(.white)
                    .font(.headline)
                Spacer()
            }
            .padding(20)

        }

    }

    func flagTapped(_ number: Int) {
        if number == correctAnswer {
            userScore += 1
            scoreTitle = "Correct"
        } else {
            scoreTitle = "Wrong! That's the flag of \(countries[number])"
        }

        showingScore = true
        // this counter increment could also go in askQuestion function
        if currentQuestion < 8 {
            currentQuestion += 1
        } else {
            gameOver = true
        }

    }

    func askQuestion() {
        countries.shuffle()
        correctAnswer = Int.random(in: 0...2)
    }

    func resetGame() {
        userScore = 0
        currentQuestion = 1
    }

}

Guess the Flag

2      

@tomn

Good job, pal ! You got the result, the app is working. Soon you'll catch up new things and will be able to add more functionality to your creation so do not rush. Believe me.

Tried to use a computed property for question counter but couldn't quite get it. Just resorted to a simple incrementer in one of the functions.

Would have liked to add functionality to make sure the same flag isn't asked twice in a row, or twice in a game of 8

Would have liked to show a question and answer tally score sheet at the end. But that would have required invoking another view, and that hasn't been covered yet.

  1. As one man here said - "keep it simple". If it works -do not bother for now. You will always be able to make your code more complicated.
  2. I was thinking about that too but kept with what I have. For me the better solution was not in making such a restriction but just to increase the amount of Countries and Flags in the library. The more of them we have, the less is the chance to get same countries in next round after the shuffle : )
  3. For now it's still good to have an alert with notification. Yes, it's not so fancy, but it get's the job done. And again, you will learn how to do this later. Don't worry. Time will come.

For now just keep the pace and continue your journey ! Well done !

3      

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

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.