Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

iOS Intermediate Swift Properties Property Observers

Jesse Gay
seal-mask
.a{fill-rule:evenodd;}techdegree
Jesse Gay
Full Stack JavaScript Techdegree Student 14,045 Points

I passed challenge, but Xcode still throws errors.

I passed the challenge, but when I try to run it in Xcode I get many errors. If I add import UIKit at the beginning, then most of the errors get fixed, but I still have two errors (listed below.) Does anyone know why I'm getting the errors?

Playground execution failed:

error: S1_Properties.playground:23:1: error: 'required' initializer 'init(coder:)' must be provided by subclass of 'UIViewController'

^

error: S1_Properties.playground:21:9: error: must call a designated initializer of the superclass 'UIViewController' super.init() ^

observer.swift
class TemperatureController: UIViewController {
    var temperature: Double {
        didSet {
            if temperature > 80.0  {
                view.backgroundColor = .red
            }   else if temperature < 40.0  {
                view.backgroundColor = .blue
            }   else  {
                view.backgroundColor = .green
            }
        }
    }

    init(temperature: Double) {
        self.temperature = temperature
        super.init()
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
    }
}

2 Answers

Michael Hulet
Michael Hulet
47,912 Points

The issues you're seeing aren't really relevant to this challenge, as they have to do with the way initializers work in Swift. The challenge code has this issue by default.

Swift has 2 types of initializers: designated and convenience. A convenience initializer is declared with the convenience keyword, like this:

convenience init(){
    // Do stuff to construct an object
    self.init(something: nil)
}

A convenience initializer must:

  • Call a different initializer from the same class
  • Eventually call a designated initializer for the class

A designated initializer is just a normal initializer declared with the init keyword and nothing else. A designated initializer must directly call another designated initializer on it's class's superclass. You're likely really used to designated initializers, as every class must have at least one, though classes generally have very few (likely only one or two), and they can be inherited in specific cases where a new initializer isn't necessary. A class isn't required to have any convenience initializers, though they can be useful to do things like provide default values to designated initializers or provide specific values for specific cases.

Since the initializer in the challenge's code isn't declared with the convenience keyword, it's a designated initializer, which fulfills the language's requirement of this class needing one, since there's a new property that needs to be initialized. Since it's a designated, it must call a designated initializer from its superclass, and UIViewController has exactly 2 designated initializers: init?(coder:) and init(nibName:bundle:). Note that the plain old init() isn't in that list. To get rid of the 2nd error message, you'll have to change the call to super.init() to a call to either super.init?(coder:) or super.init(nibName:bundle). Personally, I'd pass nil to both parameters of super.init(nibName:bundle:).

When you're writing an initializer, you declare it with the required keyword to tell Swift that any subclass that it determines needs to implement its own initializer must also at least implement its own overridden version of the initializer you're writing. UIViewController's init?(coder:) is a required initializer, so since this challenge's subclass needs to implement its own initializer, it also must provide its own implementation of init?(coder:). This initializer is called to construct an instance of your class from a serialized representation of it, such as a property list, JSON file, or storyboard/.xib file.

An implementation that would fix both of these errors at the same time would look something like this:

class TemperatureController: UIViewController {
    var temperature: Double

    init(temperature: Double) {
        self.temperature = temperature
        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder: NSCoder) {
        temperature = coder.decodeDouble(forKey: "temperature")
        super.init(coder: coder)
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
    }
}

The above implementation changes the call to super.init() to a call to super.init(nibName:bundle:) and passes nil to both arguments, and also provides a simple default implementation for init?(coder:) which looks up the value the NSCoder provides for the key "temperature" and assigns it to the property temperature and then passes control to super.init(coder:). However, I don't think this is the easiest/best way to do this. Here's a way we can cut down on some code:

class TemperatureController: UIViewController {
    var temperature: Double = 0

    convenience init(temperature: Double) {
        self.init()
        self.temperature = temperature
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
    }
}

In this code, temperature is assigned a default value at its declaration, and since this class doesn't add any other properties, this signals to Swift that this class doesn't need any custom designated initializers, and it's fine for it to inherit the ones from its superclass. That clears the way for us to mark the initializer that the challenge implements as a convenience initializer. Since it's a convenience initializer, it's free to call other convenience initializers on itself, such as its plain old self.init(), which is a convenience initializer that it inherited from its superclass, UIViewController. This way, we have to write less code, initialization is a bit more swifty, the compiler's happy, and our code works just fine. If we want an initializer to set the temperature at initialization-time, this code is great. However, we can make it just a bit shorter:

class TemperatureController: UIViewController {
    var temperature: Double = 0

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
    }
}

Remember how I said that assigning a default value to temperature signals to Swift that our subclass doesn't need to implement its own designated initializer? Since we're not doing anything special to calculate temperature's value at initialization and just assigning what's passed in straight to the property, we actually don't need any initializer at all. Swift won't generate a default init(temperature:) for us, so we'd have to assign to temperature as a property after creating a new instance, but we get rid of errors and compress initializer code down to just 2 extra characters that we didn't have before