Day 62 here, reporting back from stress-land!
Trying to accomplish six contemporary jobs together AND also learning how to code… I am sure I am going to explode quite soon… Until then, though… here is the review of what I learned during Project 15.
Review
- The identity transform resets views to their default size, position, and rotation.
UIKit
uses ease in, ease out animations by default.UIKit
has a special clear color that is transparent.- The
sender
parameter tells us whichUIKit
control triggered a method. - When creating a scaling
CGAffineTransform
we can provide different X and Y values. - A scale transform of X:0.5 Y:0.5 makes a view 50% of its default size.
- We can add a delay to animations so they start after a few seconds.
- In
UIKit
, making a translation transform with a negative Y value moves it up the screen (after all those years in geometry school that’s so easy and convenient right?). - An
alpha
value of 1 means “fully visible”. - Spring animations cause their values to overshoot and bounce.
- The
break
keyword exits the current loop orswitch
block. - We can control the center of a view using its
center property.
Challenges
Challenge 1: go back to project 8 and make the letter group buttons fade out when they are tapped. We were using the isHidden
property, but we’ll need to switch to alpha
because isHidden
is either true or false, it has no animatable values between.
This isn’t too bad, in theory, it all depends from how deep you want to go down the rabbit’s hole! I started very calmly, adding this line to letterTapped
:
UIView.animate(withDuration: 1) { sender.alpha = 0 }
and this line to the else
block of the submitTapped
method, to the for
loop inside the clearTapped
one and to the showLetterButtons
method:
UIView.animate(withDuration: 1, animations: { button.alpha = 1 })
This is as far as one could go without stretching anything but the point is that, in its current state, the button first flashes because it is being selected than starts its animation.
The solution to this issue, as suggested to me by Rob Baldwin (@isSwifty on Twitter), is to create a custom subclass of UIButton
. Not really sure why this works but it does…
So… create a new Cocoa Touch Class, subclassing UIButton
and calling it “LetterButton”. Inside simply write this:
override func setTitle(_ title: String?, for state: UIControl.State) {
UIView.performWithoutAnimation {
super.setTitle(title, for: state)
self.layoutIfNeeded()
}
}
Then proceed with the following updated, starting from the top of ViewController.swift:
- change the type of the
letterButtons
andactivatedButtons
arrays to be of type[LetterButton]
. - at the end of the
loadView
method, when we create the buttons, change theletterButton
creation to be aLetterButton()
and add this line:letterButton.setTitleColor(.blue, for: .normal)
(even if this is not really the same blue of the system’s one). - change the
sender
’s type for theletterTapped
method to be of typeLetterButton
The thing that scares me here is that at first I had forgotten to change the letterButtons
array to be of type [LetterButton]
and… nothing… it didn’t crash, it didn’t complain…
This is really a mystery…
Also, I tried to change the title’s color to match the default one but with no luck… again… looking for solutions, reading questions, not understanding anything… a bunch of time lost because that is not learning!
Thankfully Rob came in my help once more and explained me that each value of the colour need to be a fraction of 1.0 so I needed to write:
letterButton.setTitleColor(UIColor(red: 75/255, green: 142/255, blue: 247/255, alpha: 1), for: .normal)
Still, this project has an issue: ever since we integrated the GCD I need to launch the app twice for it to load properly. The first time I launch it the screen is empty… So many mysteries, so many doubts… I do not know if I am asking for too much but … goddammit … just explain me things! EXPLAIN ME HOW THINGS WORK! I am learning nothing from these frustrating searches…
GitHub repo for this challenge: click here.
Challenge 2: go back to project 13 and make the image view fade in when a new picture is chosen. To make this work, set the alpha
to 0 first.
First attempt
In viewDidLoad
, set imageView.alpha = 0
In didFinishPickingMediaWithInfo
, write UIView.animate(withDuration: 2.0) { self.imageView.alpha = 1 }
Result… nothing! NOTHING!
Second attempt
Let’s interpret the instructions:
- go back to project 13: well, this was easy, right?!
- make the image view fade in when a new picture is chosen: so, when is a picture chosen? The only method that looks to me as if it could be doing that is the
imagePickerController(didFinishPickingMediaWithInfo:)
as no other method makes any sense for this purpose. Let’s now dive in and see what happens: we check that we have an image as the second parameter and that we can convert it to anUIImage
. Then we dismiss the picker-controller and we set thecurrentImage = image
. What comes after is clearly “filtering”. So let’s go back: my instinct says that when we set the current image to be our found image it is already too late. But I want to try to make the image-view have a 0 alpha before doing anything else so I will keep the line I had above inviewDidLoad
.
Nothing happens… I also tried to set the background color of the image view to red, so that I could see it but nothing… I realised that image view is not instantiated, unless hooking it to an outlet instantiates it? Who knows? Who explained this? No-one! So… let’s try something else.
Third attempt
In the storyboard I set the image-view’s background color to red (instead of doing that in code). Running it shows this bright red rectangle, which means that, in z-Position, the imageView is on top of the view (this I knew, fine fine…).
Now I can reduce its alpha to 0 and animate it, but I want to do that in viewDidLoad
just to test.
Fine, this works. It seems that the proper way of doing this is to set the image-view’s alpha to 0 in the storyboard. I guess I could do that also in code.
Let’s try it just to be sure.
Good, it works in the same way. I will keep it in the code as it is kind of safer now.
I now tried to put the animation code inside the did-finish-blablabla method and I saw that it doesn’t really help. When the picker is dismissed the view is already full red and the image already there. There must be something going on with the dismiss.
Fourth attempt
That was it!
I set back the default background colour of the imageView in the storyboard and, inside the did-finish-blabla method, replaced the dismiss
call with this:
dismiss(animated: true) {
UIView.animate(withDuration: 2.0) {
self.imageView.alpha = 1
}
}
It works!
GitHub repo for this challenge: click here.
Challenge 3: go back to project 2 and make the flags scale down with a little bounce when pressed.
I went like a truck on this but solving only the assigned thing didn’t satisfy me so I pushed myself a bit more.
Inside buttonTapped
I created a closure with a closure inside (🤯) so that it would animate a down when pressed and back up alone on its own! The result was quite pleasing.
I also refactored a bit my code, moving outside of this method all the answer checking logic to its own checkAnswer
method.
So we end up with a buttonTapped
action that looks like this:
@IBAction func buttonTapped(_ sender: UIButton) {
UIView.animate(withDuration: 0.4, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 5, options: [], animations: {
sender.transform = CGAffineTransform(scaleX: 0.85, y: 0.85)
}, completion: { _ in
UIView.animate(withDuration: 0.6, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 5, options: [], animations: {
sender.transform = CGAffineTransform(scaleX: 1, y: 1)
}, completion: { _ in
self.checkAnswer(answer: sender.tag)
})
})
}
I voluntarily did not touch the “usingSpringWithDamping” and “initialSpringVelocity” parameter as they are not fully clear to me. The Documentation doesn’t help too much here so I decided to go down a safe path.
For your interest, I paste here the code for the checkAnswer
method, just in case:
func checkAnswer(answer: Int) {
var title: String
if answer == correctAnswer {
title = "Correct"
score += 1
} else {
title = "Wrong! That's the flag of \(countries[answer].uppercased())"
score -= 1
}
if askedQuestions < 10 {
let alertController = UIAlertController(title: title, message: "Your score is \(score).", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Continue", style: .default, handler: askQuestion))
present(alertController, animated: true)
} else {
if score > highScore {
highScore = score
save()
let highScoreAC = UIAlertController(title: "Game over! New High Score", message: "Your score is \(score).", preferredStyle: .alert)
highScoreAC.addAction(UIAlertAction(title: "Start new game!", style: .default, handler: startNewGame))
present(highScoreAC, animated: true)
} else {
let finalAlertController = UIAlertController(title: "Game over!", message: "Your score is \(score).", preferredStyle: .alert)
finalAlertController.addAction(UIAlertAction(title: "Start new game!", style: .default, handler: startNewGame))
present(finalAlertController, animated: true)
}
}
}
So, that’s it for these challenges!
They took me two hour and a half to complete and I will keep reporting the time because to me it is becoming increasingly difficult to find the time to do this but I really want to so I will squeeze every minute I have even from more important things.
Thank you for keeping up with me until here!
Tomorrow’s going to be the heaviest day of the week for me so I think I will just barely start the 6th consolidation day (🤦♂️) and continue on Saturday.
You can find the code for this last challenge here.
Please don’t forget to drop a hello and a thank you to Paul for all his great work (you can find him on Twitter) and don’t forget to visit the 100 Days Of Swift initiative page. Also be sure to follow Rob Baldwin on Twitter, he is an awesome guy and very helpful!
Thanks for reading!
Till the next one!
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, ScoreExchange and on Apple Books).
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!