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 trialJesse Gay
Full Stack JavaScript Techdegree Student 14,045 PointsI 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() ^
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
47,913 PointsThe 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
Jesse Gay
Full Stack JavaScript Techdegree Student 14,045 PointsThanks Michael! I appreciate your thoughtful and thorough response.