A simple SwuiftUI game to learn flags.
- Stacks: vertical, horizontal and z axis
- The Color view: Color.red. To use it as a background color, use a ZStack:
ZStack {
Color.gray.edgesIgnoringSafeArea(.all)
VStack { }
}
- Color view’s frame modifier
Color.blue.frame(width: 20, height: 20)
- Custom color for the Color view
Color(red: 1, green: 0.8, blue: 0, opacity: 0.9)
- Gradient views
LinearGradient(gradient: Gradient(colors: [Color.white, Color.gray]), startPoint: .top, endPoint: .bottom)
RadialGradient(gradient: Gradient(colors: [Color.gray, Color.white]), center: .top, startRadius: 20, endRadius: 400).edgesIgnoringSafeArea(.all)
AngularGradient(gradient: Gradient(colors: [Color.white, Color.black]), center: .topTrailing).edgesIgnoringSafeArea(.all)
- Buttons & Image with the systemName initializer - using SF Symbols eg (systemName: “pencil”)
Button(action: { print("hit") }) {
HStack {
Image(systemName: "pencil")
Text("Edit")
}
}
- Alerts need a @State variable to track when they are shown
struct ContentView: View {
@State private var showingAlert = false
var body: some View {
Button(action: {
self.showingAlert = true
}) {
HStack {
Image(systemName: "pencil.circle")
Text("Edit")
}
}.alert(isPresented: $showingAlert) {
Alert(title: Text("Edit Mode"), message: Text("You have entered edit mode."), dismissButton: .default(Text("OK")))
}
}
}
- Completion handler on Alert
Alert(title: Text(scoreTitle), message: Text("Your score is ??"), dismissButton: .default(Text("Continue")) { self.askQuestion() })
- .clipShape(Shape) modifier
Image(self.countries[number])
.renderingMode(.original)
.clipShape(Capsule())
.overlay(Capsule().stroke(Color.black, lineWidth: 1))
.shadow(color: .black, radius: 2)
- Add an @State property to store the user’s score, modify it when they get an answer right or wrong, then display it in the alert.
- Show the player’s current score in a label directly below the flags.
- 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.
Go back to project 2 and create a FlagImage() view that renders one flag image using the specific set of modifiers we had.
For the full list of Code Actions to appear (on cmd + click on the view), the Canvas has to be enabled. I used the Extract Subview Code Action, however I had to add a string property to the View, to pass the country name.
struct FlagImage: View {
var country: String
var body: some View {
Image(country)
.renderingMode(.original)
.clipShape(Capsule())
.overlay(Capsule().stroke(Color.black, lineWidth: 1))
.shadow(color: .black, radius: 2)
}
}
- When you tap the correct flag, make it spin around 360 degrees on the Y axis.
- Make the other two buttons fade out to 25% opacity.
- And if you tap on the wrong flag? Well, that’s down to you – get creative!
// Challenge #1
@State private var isFlipAnimated: [Bool] = [false, false, false]
And then the 3D rotation view modifier will be triggered when the corresponding Bool value changes.
ForEach(0 ..< 3) { number in
Button(action: {
self.flagTapped(number)
}) {
FlagImage(country: self.countries[number])
}.rotation3DEffect(.degrees(self.isFlipAnimated[number] ? 360 : 0), axis: (x: 0, y: 1, z: 0))
This is triggered in the the flagTapped function:
withAnimation(.default) {
isFlipAnimated[number] = true
}
And finally - when a new round starts, I revert back to false values for all 3 buttons:
isFlipAnimated = [false, false, false]
2. A similar solution to make the other 2 buttons opaque, but I’m tracking the state in a different array.
3. For the final challenge, I’ve decided to hide the incorrect flags and leave the correct answer to be displayed.
Again, I’m using a third @State variable to track the required state. And if the wrong flag is selected, the two incorrect flags are going to be updated with the .opacity view modifier.
.opacity(self.isOpacityAnimated[number] ? (self.isWrongAnswer ? 0 : 0.25) : 1)
We will provide a custom label for each flag image/button - describing the aspect of the flag. We will store these descriptions in a dictionary:
let labels = [
"Estonia": "Flag with three horizontal stripes of equal size. Top stripe blue, middle stripe black, bottom stripe white",
...
]
...
.accessibility(label: Text(self.labels[self.countries[number], default: "Unknown flag"]))