iOS User Interface Development Approaches
September 10th, 2015
iOS User Interface Development Approaches
In this article, I’ll explore some of the gritty details of Swift initializers. Why take on such an exciting topic? Mainly because I’ve occasionally found myself confused by compilation errors when trying to create a subclass of UIView, or when trying to create a Swift version of an old Objective-C class, for example. So I decided to dig deeper to gain a better understanding of the nuances of Swift initializers. Since my background/experience has mostly involved working with Groovy, Java, and Javascript, I’ll compare some of Swift’s initialization mechanisms to those of Java & Groovy. I encourage you to fire up a Playground in XCode and play with the code examples yourself. I also encourage you to read through the Initialization section of the Swift 2.1 Language guide, which is the primary source of information for this article. Finally, my focus will be on reference types (classes) rather than value types (structs).
An initializer is a special type of method in a Swift struct, class, or enum that is responsible for making sure a newly created instance of the struct, class, or enum is fully initialized before they are ready to be used. They play the same role as that of a “constructor” in Java and Groovy. If you’re familiar with Objective-C, you should note that Swift initializers differ from Objective-C initializers in that they do not return a value.
One of the first things to keep in mind is that “Swift subclasses do not inherit their superclass initializers by default”, according to the language guide. (The guide explains that there are scenarios where superclass initializers are automatically inherited. We’ll cover those exceptional scenarios later). This is consistent with how Java (and by extension, Groovy) works. Consider the following:
class Person {
var age:Int
let name:String
init(age:Int, name:String) {
self.age = age
self.name = name
}
}
class Musician : Person {
var instrument:String
init(instrument:String) {
self.instrument = instrument
super.init(age: 0, name: "Unknown")
}
}
//the following will cause a compilation error.
//The Musician class does not have an initializer init(age:name:), although
// it's parent class does. Subclasses do not inherit initializers unless certain conditions are met.
let zappa = Musician(age:22, name:"Frank Zappa")Just as with Java & Groovy, it makes sense that this is not allowed (although, as with most things, there may be some argument on this point. See this StackOverflow post). If it were allowed, the initialization of the “instrument” property of Musician would be bypassed, potentially leaving your Musician instance in an invalid state. However, with Groovy I normally wouldn’t bother with writing initializers (i.e., constructors). Rather, I’d just use the map constructor Groovy provides implicitly, which allows you to freely pick and choose which properties you want to set upon construction. For example, the following is perfectly valid Groovy code:
class Person {
Integer age
String name
}
class Musician extends Person {
String instrument
}
def regularJoe = new Person(name: "Steve")
def xylophonist = new Musician(age: 23, instrument: "Xylophone")Notice that you can include any property, including those provided by superclasses, but you don’t have to specify all (or any) of them, and they don’t have to be specified in any particular order. This kind of ultra-flexible initializer is not provided by Swift. The closest thing in Swift is the automatically-provided memberwise initializers for structs. But the order of the arguments in a memberwise initializer is significant, even though they’re named, and depends on the order in which they are defined:
struct Size {
var width = 0.0
var height = 0.0
}
let twoByTwo = Size(width: 2.0, height: 2.0)
let fourByFour = Size(height:4.0, width:4.0) //Error: Argument 'width' must precede argument 'height'Anyway, back to classes - Groovy’s philosophy regarding post-construction object validity is clearly very different from Swift’s. This is just one of many ways in which Groovy differs from Swift.
Before we get too far down the rabbit hole, we should clarify an important concept: In Swift, an initializer is categorized as being either a designated or a convenience initializer. I’ll try to explain the difference both conceptually and syntactically. Each class must have at least one designated initializer, but may have several (“designated” does not imply “single”). A designated initializer is considered a primary initializer. They’re the head-honchos. They are ultimately responsible for making sure that all properties are initialized. Because of that responsibility, it can sometimes become a pain to use a designated initializer all the time, since they may require several arguments. Imagine working with a class that has several properties, and you need to create several instances of that class that are almost identical except for a one or two properties. (For the sake of argument, let’s also assume that there are no sensible defaults that could have been assigned to properties when they are declared). For example, let’s say our Person class also had eatsFood and enjoysMusic properties. Of course, those two things would be set to true most of the time, but you never know. Take a look:
class Person {
var age:Int
let name:String
var eatsFood: Bool
var enjoysMusic: Bool
init(age:Int, name:String, eatsFood:Bool, enjoysMusic:Bool) {
self.age = age
self.name = name
self.eatsFood = eatsFood
self.enjoysMusic = enjoysMusic
}
init(isUndead:Bool) {
self.name = "Dracula"
self.age = isUndead ? 3000 : 0
self.enjoysMusic = false
self.eatsFood = false
//The following is not allowed. A designated initializer may not delegate to another initializer in the same class.
// self.init(age: isUndead ? 3000 : 0, name:"Dracula", eatsFood:false, enjoysMusic:false)
}
convenience init(age:Int, name:String, enjoysMusic:Bool) {
self.init(age:age, name:name, eatsFood:true, enjoysMusic:true)
}
convenience init(age:Int, name:String) {
self.init(age:age, name:name, enjoysMusic:true)
}
}
class Musician : Person {
var instrument:String
init(instrument:String) {
self.instrument = instrument
super.init(isUndead:false)
//The following is not allowed. A designated initializer may only delegate up to a designated initializer
// of the immediate superclass, but here we're trying to delegate up to a convenience initializer of the superclass.
// super.init(age: 0, name: "Unknown", enjoysMusic:true)
}
}Now our Person class has four properties that have to be set, and we have a designated initializer that can do the job. The designated initializer is that first one, the one that takes 4 arguments. Most of the time, those last two arguments are going to have the value “true”. It’d be a pain to have to keep specifying them every time we want to create a typical Person. That’s where the last two initializers come in, the ones marked with the convenience modifier. This pattern should look familiar to a Java developer. If you have a constructor that takes more arguments than you really need to deal with all the time, you can write simplified constructors that take a subset of those arguments and provide sensible defaults for the others. The convenience initializers must delegate either to another, perhaps less-convenient, convenience initializer or to a designated initializer. Ultimately a designated initializer must get involved. Further, if this is a subclass, the designated initializer must call a designated initializer from it’s immediate superclass.
One real-world example for the use of the convenience modifier comes from UIKit’s UIBezierPath class. I’m sure you can imagine there are several ways of specifying a path. As such, UIBezierPath provides several convenience initializers:
public convenience init(rect: CGRect) public convenience init(ovalInRect rect: CGRect) public convenience init(roundedRect rect: CGRect, cornerRadius: CGFloat) public convenience init(roundedRect rect: CGRect, byRoundingCorners corners: UIRectCorner, cornerRadii: CGSize) public convenience init(arcCenter center: CGPoint, radius: CGFloat, startAngle: CGFloat, endAngle: CGFloat, clockwise: Bool) public convenience init(CGPath: CGPath)
Earlier, I said a class may have multiple designated initializers. So what does that look like? One important difference, enforced by the compiler, between designated initializers and convenience initializers is that designated initializers may not delegate to another initializer in the same class (but must delegate to a designated initializer in it’s immediate superclass). Look at the second initializer of Person, the one that takes a single argument named “unDead”. Since this initializer is not marked with the convenience modifier, Swift treats it as a designated initializer. As such, it may not delegate to another initializer in Person. Try commenting out the first four lines, and uncommenting the last line. The compiler will complain, and XCode should try to help you out by suggesting that you fix it by adding the convenience modifier.
Now consider the Musician subclass of Person. It has a single initializer, and it must therefore be a designated initializer. As such, it must call a designated initializer of the immediate superclass, Person. Remember: while designated initializers can’t delegate to another initializer of the same class, convenience initializers must do so. Also, a designated initializer must call a designated initializer of it’s immediate superclass. See the language guide for more detail (and pretty graphics).
As the Swift language guide explains, there are two initialization phases. The phases are demarcated by the call to the superclass designated initializer. Phase 1 is prior to the call to the superclass designated initializer, phase 2 is after. A subclass must initialize all of it’s OWN properties in phase 1, and it may NOT set any properties defined by the superclass until phase 2. Here is a code sample, adapted from a sample provided in the language guide, showing that you must initialize the OWN properties of a subclass before invoking the superclass designated initializer. You may not access properties provided by the superclass until after invoking the superclass designated initializer. Finally, you may not modify constant stored properties after the superclass designated initializer has been invoked.
class Vehicle {
var numberOfWheels = 0
var description: String {
return "\(numberOfWheels) wheel(s)"
}
}
class Bicycle: Vehicle {
var handleBarStyle:String
override init() {
self.handleBarStyle = "Ape-hangers" //You MUST initialize the properties this subclass introduces before invoking superclass designated initializer.
// self.numberOfWheels = 2 //Not OK - can't access superclass properties before invoking superclass designated initializer
super.init()
//You can manipulate the subclass OWN properties here, but only if they are variables (var) not constants (let)
//Change handleBarStyle to a constant stored property (by replacing 'var' with 'let') and see for yourself.
self.handleBarStyle = "Ape Hangers"
numberOfWheels = 2
}
}Now that we’re convinced that subclasses generally don’t inherit initializers, and we’re clear on the meaning of and distinction between designated and convenience initializers, let’s consider what happens when you want a subclass to override an initializer from it’s immediate superclass. There are four scenarios that I’d like to cover, given that there are two types of initializer. So let’s take them one by one, with simple code examples for each case:
A designated initializer that matches a superclass designated initializer This is a typical scenario. When you do this, you must apply the override modifier. Note that this scenario is in effect even when you’re “overriding” an automatically provided default initializer (i.e., when the superclass does not define any explicit initializer. In this case, Swift provides one implicitly. Java developers should be familiar with this behavior). This automatically-provided default initializer is always a designated initializer.
//a parent class that provides an explicit designated initializer
class Parent {
var parentProperty:String
init(parentProperty:String) {
self.parentProperty = parentProperty
}
}
//a subclass that overrides the parent's explicit designated initializer
class Child : Parent {
var childProperty:Double
override init(parentProperty: String) {
self.childProperty = 0.0
super.init(parentProperty: parentProperty)
}
//Since the parent explicitly defines a designated initializer,
// it is not implicitly provided with a default no-arg initializer. Therefore,
// we do not provide the 'override' modifier if we want to provide a "default" no-arg
// initializer.
init() {
self.childProperty = 0.0
super.init(parentProperty: "nada")
}
// To further illustrate the difference between 'convenience' and 'designated' initializers, and
// initialization phases, try replacing the above default designated initializer with the following convenience initializer
// convenience init() {
// self.childProperty = 235.3 // this won't work - we can't access 'self' until after the designated initializer is invoked.
// self.init(parentProperty: "nada")
// self.childProperty = 10.0 //now we can access 'self' to further modify our own properties
// }
}
//The following two classes illustrate the case where a superclass does not define any
// initializer explicitly (and therefore is provided a 'default' initializer by Swift).
//Any subclass that wishes to override the implicit initializer must use the 'override' modifier.
class ImplicitParent {
var parentProperty:String = ""
}
class ImplicitChild : ImplicitParent {
var childProperty:Double
//Try to remove the 'override' modifier. The compiler should complain.
override init() {
self.childProperty = 0.0
// super.init() //We can optionally invoke the provided designated initializer of the parent class,
// but it isn't necessary since all of the parent's stored properties are given default
// values at declaration.
}
}A designated initializer that matches a superclass convenience initializer Now let’s suppose you want to add a designated initializer to your subclass that happens to match a convenience initializer in the parent. By the rules of initializer delegation laid out in the language guide, your subclass designated initializer must delegate up to a designated initializer of the immediate superclass. That is, you may not delegate up to the parent’s matching convenience initializer. For the sake of argument, also suppose that subclass does not qualify to inherit superclass initializers. Then, since you could never create an instance of your subclass by directly invoking the superclass convenience initializer, that matching convenience initializer is not, and could never be, involved in the initialization process anyway. Therefore, you’re not really overriding it, and the override modifier does not apply.
class Parent {
var parentProperty1:String
var parentProperty2:String
init(parentProperty1:String, parentProperty2:String) {
self.parentProperty1 = parentProperty1
self.parentProperty2 = parentProperty2
}
convenience init(parentProperty1:String) {
self.init(parentProperty1:parentProperty1, parentProperty2:"nada")
}
}
class Child : Parent {
var childProperty:Double
//here we have a designated initializer whose signature matches a parent-class convenience initializer.
// The override modifier is not used because it's not *really* overriding. If you try to add the 'override'
// modifier, the compiler will complain. Try it!
init(parentProperty1: String) {
self.childProperty = 0.0
super.init(parentProperty1: parentProperty1, parentProperty2:"default")
}
}A convenience initializer that matches a superclass designated initializer In this scenario, imagine you have a subclass that adds its own properties whose default value can be (but doesn’t have to be) computed from the values assigned to one or more parent-class properties. Suppose you also only want to have one designated initializer for your subclass. You could add a convenience initializer to your subclass whose signature matches that of a designated initializer of the parent class. In this case, your new initializer would need both the convenience and override modifiers. Here is a valid code sample to illustrate this case:
class Customer {
let name:String
var netWorth:Double
init(name:String, netWorth:Double) {
self.netWorth = netWorth
self.name = name
}
}
class Investor : Customer {
let riskTolerance:Double
init(name:String, netWorth:Double, riskTolerance:Double) {
self.riskTolerance = riskTolerance
super.init(name: name, netWorth: netWorth)
}
//here we're overriding our superclass designated initializer, but we want to make sure
// it always runs though the subclass designated initializer. So we mark it with the
// 'convenience' modifier, which simultaneously prevents it from delegating to a superclass initializer
// and forces it to delegate to the designated initializer.
convenience override init(name:String, netWorth:Double) {
self.init(name:name, netWorth:netWorth, riskTolerance:min(1.0, netWorth / 1000000.0))
}
//Alternatively, we could have gone with this approach. But we're assuming there is a good
// reason for only having one designated initializer in this class.
// override init(name:String, netWorth:Double) {
// self.riskTolerance = min(1.0, netWorth / 1000000.0)
// super.init(name:name, netWorth:netWorth)
// }
}A convenience initializer that matches a superclass convenience initializer If you want to add a convenience initializer to your subclass that happens to match the signature of a convenience initializer of your superclass, just go right ahead. As I explained above, you can’t really override convenience initializers anyway. So you’d include the convenience modifier, but omit the override modifier, and treat it just like any other convenience initializer.
One key takeaway from this section is that the override modifier is only used, and must be used, if you are overriding a superclass designated initializer. (Minor clarification to make here: if you’re overriding a required initializer, then you would use the required modifier instead of the override modifier. The required modifier implies the override modifier. See the Required Initializers section below).
Now for the aforementioned scenarios where superclass initializers are inherited. As the Swift Language Guide explains, if your subclass provides default values for all of it’s own properties at declaration, and does not define any of its own designated initializers, then it will automatically inherit all of its superclass designated initializers. Or, if your subclass provides an implementation of all of the superclass designated initializers, then it automatically inherits all of the superclass convenience initializers. This is consistent with the rule in Swift that initialization of classes (and structures) must not leave stored properties in an indeterminate state.
I stumbled across some “interesting” behavior while experimenting with convenience initializers, designated initializers, and the inheritance rules. I found that it is possible to setup a vicious cycle inadvertently. Consider the following example:
class Food {
var name: String
init(name: String) {
print("in the Food designated init")
self.name = name
}
convenience init() {
print("in the Food convenience init")
self.init(name: "[Unnamed]")
}
}
class RecipeIngredient: Food {
var quantity: Int
init(name: String, quantity: Int) {
print("in the RecipeIngredient designated init")
self.quantity = quantity
super.init(name: name)
}
//this invokes the inherited convenience initializer, which invokes this overidden
//version of the parent's designated initializer, which invokes the inherited convenience initializer,
// cycle repeats until crash.
override convenience init(name: String) {
print("in the RecipeIngredient convenience init")
self.init()
}
}
let mint = RecipeIngredient(name: "mint")The RecipeIngredient class overrides all of the Food class designated initializers, and therefore it automatically inherits all of the superclass convenience initializers. But the Food convenience initializer reasonably delegates to it’s own designated initializer, which has been overridden by the RecipeIngredient subclass. So it is not the Food version of that init(name: String) initializer that is invoked, but the overridden version in RecipeIngredient. The overridden version takes advantage of the fact that the subclass has inherited Food’s convenience initializer, and there it is - you have a cycle. I don’t know if this would be considered a programmer-mistake or a compiler bug (I reported it as a bug: https://bugs.swift.org/browse/SR-512 ). Imagine that Food is a class from a 3rd party, and you don’t have access to the source code so you don’t know how it’s actually implemented. You wouldn’t know (until runtime) that using it in the way shown in this example would get you trapped in a cycle. So I think it’d be better if the compiler helped us out here.
Imagine you’ve designed a class that has certain invariants, and you would like to enforce those invariants from the moment an instance of the class is created. For example, maybe you are modeling an Invoice and you want to make sure the amount is always non-negative. If you added an initializer that takes an amount argument of type Double, how could you make sure that the you don’t violate your invariant? One strategy is to simply check if the argument is non-negative. If it is, use it. Otherwise, default to 0. For example:
class Invoice {
let amount:Double
init(amount:Double) {
self.amount = (amount >= 0) ? amount : 0
}
}This would work, and may be acceptable if you document what your initializer is doing (especially if you plan on making your class available to other developers). But you might have a hard time defending that strategy, since it does kind of sweep the issue under the rug. Another approach that is supported by Swift would be to let initialization fail. That is, you’d make your initializer failable.
As this article describes, failable initializers were added to Swift as a way to eliminate or reduce the need for factory methods, “which were previously the only way to report failure” during object construction. To make an initializer failable, you simply append the ? or ! character after the init keyword (i.e., init? or init! ). Then, after all the properties have been set and all the other rules regarding delegation have been satisfied, you’d add some logic to verify that the arguments are valid. If they are not valid, you trigger an initialization failure with return nil. Note that this does not imply that the initializer is ever returning anything. Here’s how our Invoice class might look with a failable initializer:
class Invoice {
let amount:Double
init?(amount:Double) {
self.amount = amount
if amount < 0 { return nil }
}
}
if let badInvoice = Invoice(amount: -112.34) {
print("well this is unexpected")
}
else {
print("you can't create an invoice for a negative amount, pal!")
}Notice anything different about how we’re using the result of the object creation? It’s like we’re treating it as an optional, right? Well, that’s exactly what we’re doing! When we use a failable initializer, we’ll either get nil (if an initialization failure was triggered) or an Optional(Invoice). That is, if initialization was successful we’ll end up with an Optional that wraps the Invoice instance we’re interested in, so we have to unwrap it. (As an aside, note that Java also has Optionals as of Java 8).
Failable initializers are just like the other types of initializers we’ve discussed with respect to overriding and delegation, designated vs convenience, etc… In fact, you can even override a failable initializer with a nonfailable initializer. You cannot, however, override a nonfailable initializer with a failable one.
You may have noticed failable initializers from dealing with UIView or UIViewController, which both provide a failable initializer init?(coder aDecoder: NSCoder). This initializer is called when your View or ViewController is loaded from a nib. It is important to understand how failable initializers work. I strongly recommend you read through the Failable Initializers section of the Swift language guide for a thorough explanation.
The required modifier is used to indicate that all subclasses must implement the affected initializer. On the face of it, that sounds pretty simple and straightforward. It can get a bit confusing at times if you don’t understand how the rules regarding initializer inheritance discussed above come into play. If a subclass meets the criteria by which superclass initializers are inherited, then the set of inherited initializers includes those marked required. Therefore, the subclass implicitly satisfies the contract imposed by the required modifier. It does implement the required initializer(s), you just don’t see it in source code.
If a subclass provides an explicit (i.e., not inherited) implementation of a required initializer, then it is also overriding the superclass implementation. The required modifier implies override, so the override modifier is not used. You may include it if you want to, but doing so would be redundant and XCode will nag you about it.
The Swift language guide doesn’t say much about the required modifier, so I prepared a code sample (see below) with comments to explain it’s purpose and describe how it works. For more information, see this article by Anthony Levings.
//Here we define a superclass with two designated initializers, one of which is required,
// along with a required convenience initializer.
class Player {
let name:String
var age:Int
var weight:Int
init(name:String, age:Int, weight:Int) {
self.name = name
self.age = age
self.weight = weight
}
//a required designated initializer
required init(name:String, age:Int) {
self.name = name
self.age = age
self.weight = 200
}
//a required convenience initializer
required convenience init(name:String) {
self.init(name:name, age:30)
}
}
//This class provides default values for all stored properties at declaration.
//Therefore, it meets the criteria for inheriting initializers from it's superclass,
// which means it does not have to provide an explicit implementation of the required initializer.
class RunningBack : Player {
var fumblePr:Double = 0.15 //the probability of a fumble during a play
var tackleBreaking:Double = 0.25 //the ability to break tackles
}
//the 'required' init(name:age:) initializer is inherited by RunningBack
let speedy = RunningBack(name: "Speedy McGhee", age: 30)
// ... the required convenience initializer, init(name:) is inherited as well
let powerhouse = RunningBack(name: "S. Brickhouse")
//This class does not provide default values for it's own stored properties at declaration,
//and so it must provide explicit initializer implementations (it does not meet criteria for initializer inheritance).
// Notice that it only has to provide explicit implementations of the two designated initializers of
//it's immediate superclass, Player. Doing that makes it eligible to inherit the required convenience
//initializer init(name:), so we don't have to provide an explicit implementation of that one.
class QuarterBack : Player {
var passerRating:Int
//here we must use the 'override' modifier because this designated initializer is not marked 'required'
override init(name:String, age:Int, weight:Int) {
self.passerRating = 50
super.init(name: name, age: age, weight:weight)
}
//here we override the 'required' designated initializer. Note that the 'required'
// modifier implies override, so we don't say "required override"
required init(name:String, age:Int) {
self.passerRating = (age > 30) ? 25 : 50
super.init(name:name, age:age)
}
}
//this shows that the Quarterback class inherited the required convenience initializer, init(name:),
//because it provided explicit implementations of all its superclass designated initializers.
let passerguy = QuarterBack(name: "Paul Passerman")
//This class also does not provide default values for all of its own stored properties, and
// does not override all of the superclass designated initializers. Therefore, it does not
//inherit the required convenience initializer, init(name:). Therefore it must provide an
//explicit implementation for it.
class Kicker : Player {
let fieldGoalPercentage:Int
//We are forced to provide an explicit implementation of this required initializer
//because we won't meet the criteria for inheriting it.
required init(name:String, age:Int) {
self.fieldGoalPercentage = 50
super.init(name: name, age: age)
}
//... and since we don't override all the superclass designated initializers, we don't
//get to inherit the required convenience initializer either, so we have to provide it.
required convenience init(name:String) {
self.init(name:name, age:30) //remember - a convenience initializer must delegate to a designated initializer of it's own class.
}
}
let kicker = Kicker(name: "Laces Outman")One of the things that prompted me to dig deep into Swift initializers was my attempt to figure out a way to create a set of designated initializers without duplicating initialization logic. For example, I was working through this UIView tutorial by Ray Wenderlich, converting his Objective-C code into Swift as I went (you can take a look at my Swift version here). If you look at that tutorial, you’ll see that his RateView subclass of UIView has a baseInit method that both of the designated initializers use to perform common initialization tasks. That seems like a good approach to me - you don’t want to duplicate that stuff in each of those initializers. I wanted to recreate that technique in my Swift version of RateView. But I found it difficult because a designated initializer cannot delegate to another initializer in the same class and cannot call methods of its own class until after it delegates to the superclass initializer. At that point, its too late to set constant properties. Of course, you could work around this limitation by not using constants, but that is not a good solution. So I figured it was best to just provide default values for the stored properties where they are declared. That’s still the best solution that I currently know of. However, I did figure out an alternative technique that uses initializers.
Take a look at the following example. This is a snippet from RateViewWithConvenienceInits.swift, which is an alternate version of my Swift port of RateView. Being a subclass of UIView that does not provide default values for all of it’s own stored properties at declaration, this alternate version of RateView must at least provide an explicit implementation of UIView’s required init?(coder aDecoder: NSCoder) initializer. We will also want to provide an explicit implementation of UIView’s init(frame: CGRect) initializer to make sure that the initialization process is consistent. We want our stored properties to be set up the same way regardless of which initializer is used.
//this demonstrates a kludgy (in my opinion) solution to the problem of
// creating a single common initializer in subclasses of UIView.
//this is the 'common' initializer. It is a failable designated
//initializer for our UIView subclass. It serves as the single place where stored
// properties may be initialized, if for some reason we can't provide default values
// at declaration. We mark this as failable because it may delegate to the failable superclass
// initializer, init?(coder aDecoder: NSCoder)
private init?(frame:CGRect, coder:NSCoder?) {
self.maxRating = 5
self.rating = 0.0
self.editable = false
self.midMargin = frame.width / 20
self.leftMargin = frame.width / 10
self.minImageSize = CGSizeMake(5.0, 5.0)
if let aDecoder = coder {
super.init(coder:aDecoder)
}
else {
super.init(frame:frame)
}
//optional: add logic to trigger initialization failure here
}
//here we override the matching designated initializer from UIView and make it a convenience initializer,
// then delegate to our subclass designated initializer above.
convenience override init(frame: CGRect) {
print("in the convenience override init(frame: CGRect) initializer")
//can't call any non-initializers here..
//notice we're force-unwrapping the result, since we're calling a failable initializer :/
self.init(frame:frame, coder:nil)!
//can't set constants here
}
//here we override the matching designated initializer from UIView and make it a convenience initializer,
// then delegate to our subclass designated initializer above.
convenience required init?(coder aDecoder: NSCoder) {
print("in the init(coder) convenience initializer")
//can't call any non-initializers here..
self.init(frame:CGRectZero, coder: aDecoder)
//can't set constants here
}Notice that I added the convenience modifier to the overridden versions of UIView’s initializers. I also added a failable designated initializer to the subclass, which both of the overridden (convenience) initializers delegate to. This single designated initializer takes care of setting up all the stored properties (including constants - I did not have to resort to using var for everything). It works, but I think it’s pretty kludgy. I’d prefer to just provide default values for my stored properties where they are declared, but it’s good to know this option exists if needed.
Thanks for reading! I hope you found this article useful. Please leave a comment if you have any questions or helpful suggestions.
iOS User Interface Development Approaches
ProductFlavor is a very powerful feature available in the Android gradle plugin that allows us to manage different “flavors” of an application. This is useful when you want the same application but with different features per flavor (e.g. Full…
Building a Kiosk Application in Android 5.0 (Lollipop)
Insert bio here