Hacking with Swift – Challenge 15

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 which UIKit 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 or switch 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 and activatedButtons arrays to be of type [LetterButton].
  • at the end of the loadView method, when we create the buttons, change the letterButton creation to be a LetterButton() 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 the letterTapped method to be of type LetterButton

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 an UIImage. Then we dismiss the picker-controller and we set the currentImage = 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 in viewDidLoad.

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!

Published by Michele Galvagno

Professional Musical Scores Designer and Engraver Graduated Classical Musician (cello) and Teacher Tech Enthusiast and Apprentice iOS / macOS Developer Grafico di Partiture Musicali Professionista Musicista classico diplomato (violoncello) ed insegnante Appassionato di tecnologia ed apprendista Sviluppatore iOS / macOS

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Create your website with WordPress.com
Get started
%d bloggers like this: