Silver Challenge

Added the following to viewController:

override func viewDidLoad()
{
    super.viewDidLoad()

    let gradientLayer = CAGradientLayer()
    gradientLayer.colors = [UIColor.red.cgColor,
                            UIColor.green.cgColor,
                            UIColor.blue.cgColor]
    gradientLayer.frame.size = view.frame.size
    
    view.layer.insertSublayer(gradientLayer, at:0)
}

I’m not sure what the hint was talking about wrt modifying viewWillLayoutSubview, I didn’t find it necessary to override that function to get the gradient to display correctly. Maybe that’s necessary if you add the gradient layer through Interface Builder rather than programmatically, but I couldn’t find a way to do it through IB.

1 Like

I came up with almost the exact same solution, the only difference being the way of setting the gradient layer’s frame which on my code looks as follows:

myLayer.frame = view.bounds

I was also wondering about the exact same thing regarding the viewWillLayoutSubviews(), perhaps it’s a matter of style and correctness and that’s the place where changes like these should be made? But as you said, it wasn’t necessary.

The documentation offers hints as to when viewWillLayoutSubviews might come in handy. The key is the change in bounds.

// Declaration:
viewWillLayoutSubviews()

Called to notify the view controller that its view is about to layout its subviews.

// Discussion
When a view’s bounds change, the view adjusts
the position of its subviews.

Your view controller can override this method
to make changes before the view lays out its subviews.

The default implementation of this method does nothing.

1 Like

At first I didn’t think I’d ever need to update the gradient subview since it filled the whole screen & that would never resize.

Then I tried rotating the phone. Oh, that’s when.

So now the view controller looks like this:

class ViewController: UIViewController {

    var gradientLayer: CAGradientLayer = CAGradientLayer()

    override func viewDidLoad()
    {
        super.viewDidLoad()

        gradientLayer.colors = [UIColor.red.cgColor,
                                UIColor.green.cgColor,
                                UIColor.blue.cgColor]
        gradientLayer.frame.size = view.frame.size
    
        view.layer.insertSublayer(gradientLayer, at:0)
    }

    override func viewWillLayoutSubviews()
    {
        super.viewWillLayoutSubviews()
    
        gradientLayer.frame.size = view.frame.size
    }
}

This mostly works, except for upside down. :thinking:

Edit: Apparently there’s a project setting you need to change to get that orientation to work. It’s in the General settings, under Deployment Info. There are 4 check boxes for each of the 4 screen orientations. Upside Down is the only one not checked by default. Check that & rebuild, and then upside down works.

They should have made this the gold challenge, it wound up being more involved than implementing equal spacing.

4 Likes

I’m new to iOS Development. I was having trouble with the Silver challenge, I read through the chapter again and I also attempted to read the documentation, but I didn’t understand how to provide the solution? The documentation is really confusing also, can anyone please offer any insight into how they came up with the solution and offer any guidance to help me understand as well? Thanks in advance.

First, they explicitly tell you you’re going to add a CAGradientLayer to the view. Looking back at the section earlier in the chapter where we added rectangles to the view, I knew I would be doing that in viewDidLoad. Looking at the documentation for CAGradientLayer told me how to set up the colors for the gradient. Poking around a bit in the documentation for CALayer I figured out how to set the frame size.

Figuring out how to add the layer behind the other components took a few tries, but I eventually figured that out from looking at the documentation for UIView.

For the last bit I needed a bit of help here which you can read above.

Thank you! I will re-read and try to come up with the solution again.

How come addSublayer does not work here? Any ideas?

If I’m remembering correctly, addSubLayer put the gradient on top of everything else instead of behind. That’s because addSubLayer adds the new layer to the end of the sublayers array, and the items in the sublayers array are drawn in order - sublayers[0] is drawn first, then sublayers[1] is drawn on top of that, and so on. If the gradient is at the end of the array it is drawn last, blocking out everything that was drawn before it, so you have to make sure it gets drawn first by inserting it at the beginning of the array. And because we’re adding the gradient in viewDidload, the other items on the screen are already in the array.

1 Like

I make code like this.

class ViewController: UIViewController {

     let gradientLayer = CAGradientLayer()
    
    override func viewDidLoad() {
        super.viewDidLoad()

        gradientLayer.frame = view.bounds
        gradientLayer.colors = [ UIColor.purple.cgColor, UIColor.blue.cgColor, UIColor.green.cgColor, 
  UIColor.yellow.cgColor, UIColor.orange.cgColor, UIColor.red.cgColor]

        // Rasterize this static layer to improve app performance.
        gradientLayer.shouldRasterize = true
        view.layer.addSublayer(gradientLayer)
       

    }
    
    override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()
        
    }
}

Simulator shows below. Label is hidden.

Thanks for the thorough explanation!

Hmm. I checked the Upside Down box but I’m just getting it with the “iPhone” upside down and the stuff in the display is just sideways. In fact, if I start with the simulator upside down, the whole app starts out upside down. Rotate it around, and then it’s sideways when the simulator is upside down. Same with running it on my actual iPhone.

Turns out there are two sets of settings - one for iPhone, the other for iPad. Toggling the checkbox only changes the iPhone setting. You can see both of them on the Info panel:

As for why it doesn’t always work on your iPhone, I don’t know - it seems to work consistently for me.

I even tried coping and pasting in your code to make sure I got every detail of that, yet it still occurred. Oh well. This will bite me sometime later.

Woof I had a really hard time with the silver challenge it took a lot more time than the gold challenge did. If I’m being honest it kind of discouraged me for a day, and broke down and came to forums to figure out how to do it. I just wanted to say to anybody else in a similar situation to not get discouraged and just keep learning, even if you have to rely on help. This is a really hard thing to just pickup and learn so just regroup and try to not get to down. After I reset I was able find success on the gold challenge which took way less time (maybe they should be switched?) My suggestion is if you’re feeling down maybe give the gold a try and then retry the silver with a success under your belt. If you can’t get the gold challenge don’t worry failure and frustration are the weights you need to lift to get stronger at anything you try to do. Stay positive cheers!!

3 Likes

This is gold. Couldn’t for the life of me figure out how to get the gradient when rotating the phone. Thanks for taking the time to post this. Really helped out this newb.

Can anyone explain the super.viewWillLayoutSubviews()?

In particular the ‘super’ part? Seems to be required for overriding functions. Only thing I could find in the docs pertains to inheritance and the override function in particular. It reads:

  • An overridden property called someProperty can access the superclass version of someProperty as super.someProperty within the overriding getter or setter implementation.

I guess I don’t get what a superclass is. Thanks in advance and sorry for the basics. I’m new. Ha!

The super class (or parent class as it’s sometimes called) is the class that your class inherits from. In this case you’ve defined a class called ViewController that inherits from UIViewController:

class ViewController: UIViewController {

So UIViewController is the super class of ViewController. If you want to access the functions or properties of UIViewController from within the code you’re writing for ViewController, you need to add the super. modifier on the front so the compiler knows to look for that function or attribute in the parent class. Without that, it would look for that function or property in the current class. If you changed viewWillLayoutSubviews() & left out the super. like this:

override func viewWillLayoutSubviews()
{
    viewWillLayoutSubviews()
    
    gradientLayer.frame.size = view.frame.size
}

then you’d have an infinite loop on your hands because the function in ViewController would just keep recursively calling itself, and the code in viewWillLayoutSubviews() in UIViewController would never get called.

3 Likes

I’ve pretty much landed at similar solutions to what everyone else is doing (though I was initally creating the layer and inserting it within viewWillLayoutSubviews.

The one thing I’m wondering about is how viewWillLayoutSubviews work. AFAICT this method is called when the view is first loaded or things are resized (in this case changing orientation of the phone) and is called before the subviews are created. So if I put in addSubview inside viewWillLayoutSubviews, why is it that it still adds the layer AFTER all the other layers generated from the UIViews created in the storyboard? I would’ve expected it to add the gradient layer before adding any other layers.

The description of addSubview is

Adds a view to the end of the receiver’s list of subviews.

I found that viewWillLayoutSubviews is being called after viewDidLoad, so you’d have the same problem I had with addSublayer (see my previous post about that). You’d have to use insertSubview(_:at:) to force the gradient view to be added at the beginning of the subview array.