100 Days of SwiftUI – Day 34
Learning Swift Day 219
Let’s start with the Review for project 6.
- We can use multiple
animation()
modifiers on a single view. Using several animation modifiers lets us animate a view’s state in different ways. - The
blur()
modifier applies a Gaussian blur to a view, using a radius we specify. This is all done on the GPU, so it’s really fast. rotation3DEffect()
can rotate around more than one axis.- We can send
nil
to theanimation()
modifier to disable the animation. - SwiftUI comes with several basic transitions built in.
- We can attach
onChanged()
andonEnded()
modifiers to aDragGesture
. - Ease-in animations start fast and end slow.
- SwiftUI lets us animate changes that occur as a result of modifying a Boolean’s value. It evaluates our view state before the change and after, and animates the differences.
- We can attach an
animation()
modifier to a binding. This causes changes in the binding to trigger an animation. - SwiftUI can animate several properties changing at the same time. It will animate them all simultaneously.
- Animation delays are specified in seconds. We can specify partial seconds as 1.5 and similar.
rotation3DEffect()
lets us spin a view around its X, Y and Z axes.
Other things reviewed thanks to wrong options:
- The
offset()
modifier lets us move a view relative to its natural position. - We can loop over implicit animations using
repeatCount()
orrepeatForever()
. - The
clipShape()
modifier lets us specify a shape for a view to be drawn inside. - Asymmetric transitions let us specify one transition for insertion and another for removal.
Challenges
Go back to the Guess the Flag project and add some animation:
Challenge 1: when you tap the correct flag, make it spin around 360 degrees on the Y axis.
So… I spent the last two days (and at least four hours) learning Animations in SwiftUI and now what? I come to the challenge and I am not able to do it. Nice, right?!
In my first implementation I can make the flags rotate when its correct, but all three flags rotate, not one… how can I apply an animation to only one view of a ForEach?
It seems this is not possible because of this damned state thing that governs our new life (and which I am really starting to hate). Anyway, somehow I managed to do something.
Create this property @State private var rotationAmount = 0.0
, then add this inside the flagTapped()
method:
withAnimation(.spring()) {
self.rotationAmount += 360.0
}
I used a spring, you use whatever you want! Then, after the closing brace of the Button, add this code:
.rotation3DEffect(.degrees(number == self.correctAnswer ? self.rotationAmount : 0), axis: (x: 0, y: 1, z: 0))
This makes the correct button spin and the other ones not. Bah … way harder than it should be I think … But let’s move on…
Challenge 2: make the other two buttons fade out to 25% opacity.
Now I am starting to lose my precious patience, here … I repeated the process of before and it just didn’t work. Looked at other people’s solutions, which had adopted my same strategy and even posted their solution online and … it doesn’t work! Damn it! How can I have spent 4 hours learning this, look back at the material for inspiration and find absolutely NOTHING that helps here? Shouldn’t challenges be about applying knowledge, instead of banging your head against an unbreakable wall?
So, everyone is using a modifier called .opacity
passing it (number == self.correctAnswer ? 1 : 0.25)
. If I do so, I get the result immediately and not when I press the button.
Thanks to the help of Christine Stanley I understood that the .opacity
modifier is there on the button all the time, not just when the button is pressed. Another condition was needed to check if the button had been tapped and the result was showing.
I used the showingScore
variable so the modifier looks like this…
.opacity(self.showingScore && number != self.correctAnswer ? 0.25 : 1.0)
. The only other change needed is the reset of showingScore
to false
.
It now works, I simply would like to know how to delay the appearance of the alert so that the animation is shown and completed before.
Before moving on to the next challenge I refined a bit the existing ones. I added an @State
private variable property called dimWrongFlags
initialised to false
. Then I put that property inside the opacity check. Finally, inside flagTapped()
I added dimWrongFlags = true
and a call to Dispatch the main queue asynchronously after 1 second so that it would change the showResults
to true. This makes the alert show 1 second after, allowing the animation to complete. The extra state property is needed to allow separation between the dimming of the flags and the appearance of the alerts.
Challenge 3: and if you tap on the wrong flag? Well, that’s down to you — get creative!
How much I would like to have all the time in this world to just stay in front of the screen, staring its beautiful emptiness and feel the creativity rushing and knowing what I have to do! I would also like to have access to resources that tell me exactly which steps to follow when I have to do something. I agree that the biggest part is the idea itself, but then we are stuck here.
Normally on iOS when you do something wrong the view is shaken horizontally. I would like to reproduce that.
I’m really sorry but I had to copy and implement the solution from here. No way on Earth I could have done this alone, NO WAY! And this frustrates me because if these challenges are impossible for our level, what’s the point? Have I learned something by copying? Maybe, but very little as most of the code is obscure to me. Sometimes I really feel like dropping out of this path and follow my own road, with books and slow app building. Much more enjoyable…
Anyway, I added an @State var attempts: Int = 0
property, then implemented this struct:
struct Shake: GeometryEffect {
var amount: CGFloat = 10
var shakesPerUnit = 3
var animatableData: CGFloat
func effectValue(size: CGSize) -> ProjectionTransform {
ProjectionTransform(CGAffineTransform(translationX:
amount * sin(animatableData * .pi * CGFloat(shakesPerUnit)),
y: 0))
}
}
And finally applied this modifier .modifier(Shake(animatableData: CGFloat(self.attempts)))
to the ForEach and modifier attempts
with a default animation in the else
clause of the flagTapped
method. Also, I reset attempts
to 0
in the askQuestion
method.
This is not ideal as the source of this code has another better way in a talk. I will try and learn that as soon as I get some time, which may not be really soon at all.
Quite disappointed by this day and the two days before it. Spent a whole lot of time studying, almost nothing remained, then faced challenges and felt completely stuck. It is a very bad feeling, it is not motivating me to delve any further in this, I really do not like it. Will I go on? Sure, but I have to reflect a lot on how I want to do that, as this way is too much time consuming for me.
Thank you for reading!
If you like what I’m doing here please consider liking this article and sharing it with some of your peers. If you are feeling like being really awesome, please consider making a small donation to support my studies and my writing (please appreciate that I am not using advertisement on my articles).
If you are interested in my music engraving and my publications don’t forget visit my Facebook page and the pages where I publish my scores (Gumroad, SheetMusicPlus e ScoreExchange).
You can also support me by buying Paul Hudson’s books from this Affiliate Link.
Anyways, thank you so much for reading!
Till the next one!