Skip to main content

Adding Swift Convenience Initializers

Designated or Convenience?


First a recap on the two types of initializer:
designated initializer is the primary initializer for a class. It must fully initialize all properties introduced by its class before calling a superclass initializer. A class can have more than one designated initializer.
convenience initializer is a secondary initializer that must call a designated initializer of the same class. It is useful when you want to provide default values or other custom setup. A class does not require convenience initializers.

The Three rules

With that clear you need to remember three rules for designated and convenience initializers for class types:
  • A designated initializer must call a designated initializer from the immediate superclass.
  • A convenience initializer must call another initializer from the same class.
  • A convenience initializer must ultimately call a designated initializer.
What does this mean for us? In simple terms, do not call super from your convenience initializer. Call another initializer (convenience or designated) from the same class.

    A Practical Example

    Let’s give it a try with a practical example. Creating and configuring an image view from an image in the asset catalog ready for us to use with Auto Layout needs several lines of code:
    // Create the image
    let heartImage = UIImage(named: "Heart")
    
    // Now create the image view using the image
    let heartImageView = UIImageView(image: heartImage)
    
    // Configure the view remembering to disable
    // the autoresizing mask if we are using
    // Auto Layout
    heartImageView.translatesAutoresizingMaskIntoConstraints = false
    
    // Sometime we must also override the default content mode
    heartImageView.contentMode = .scaleAspectFit
    
    // Finally add to the view hierarchy
    view.addSubview(heartImageView)
    
    This gets boring quickly and I always end up forgetting to disable the translation of the auto resizing mask.

    Creating a Convenience Initializer

    Let’s create a new UIImageView convenience initializer that performs the configuration for us. Our initializer will take the name of the image in the asset catalog and the content mode. Since we do not always want to set the content mode we will make it default to scale to fill. Here is the code:
    extension UIImageView {
      convenience init?(named name: String, contentMode: UIViewContentMode = .scaleToFill) {
        guard let image = UIImage(named: name) else {
          return nil
        }
    
        self.init(image: image)
        self.contentMode = contentMode
        translatesAutoresizingMaskIntoConstraints = false
      }
    }
    
    Some points to note:
    • If the image name parameter is invalid our initializer will fail and should return nil. You define a failable initializer with a question mark after the init keyword (init?).
    • Once we have a valid UIImage we call the designated initializer of UIImageView to create the image view.
    • We can only set properties on the UIImageView object after we have called the designated initializer.

    Example Usage

    // Default contentMode
    let heart = UIImageView(named: "Heart")
    
    // Specifying a contentMode
    let star = UIImageView(named: "Star", contentMode: .scaleAspectFit)
    
    Note that the failable initializer means the return type is an optional (UIImageView?) so we would need to unwrap it before adding it to the superview.

    Property Access Before Initializing Self

    One final note on when you can access self to modify properties. If we try to set a property of UIImageView before we have called self.init(image: UIImage?) we get an error:
    // Does not compile
    self.contentMode = contentMode
    self.init(image: image)
    
    // Use of 'self' in property access 'contentMode'
    // before self.init initializes self
    
    If you remember that a designated initializer must fully initialize all properties of its class you can maybe see why this is not allowed. Even if the compiler allowed us to do it the designated initializer would overwrite our value with the default value.

    Comments

    Popular posts from this blog

    What's the difference between enum, struct and class?

    Enums An enum is considered as a structured data type that can be modified without needing to change say a String or Int multiple times within your code, for example the below shows how easy it would be to change something by accident and forget to change it somewhere else. let myString = "test" if myString == "ttest" { // Doesn't execute because "ttest" is the value assigned to "myString" } With a enum we can avoid this and never have to worry about changing the same thing more than once enum MyEnum : String { case Test = "test" } let enumValue = MyEnum . Test if enumValue == MyEnum . Test { // Will execute because we can reassign the value of "MyEnum.Test" unless we do so within "MyEnum" } Structs I'm not sure how much you know about the MVC pattern but in Swift this is a common practise, before I explain how structs are useful I'll...

    What Is a Closure?

    Closures are self contained chunks of code that can be passed around and used in your code. Closures can capture and store references to any constants or variables from the context in which they are defined. This is know as closing over those variables, hence the name closures. Closures are use intensively in the Cocoa frameworks – which are used to develop iOS or Mac applications. Functions are a special kind of closures. There are three kinds of closures: global functions  – they have a name and cannot capture any values nested functions  – they have a name and can capture values from their enclosing functions closure expressions  – they don’t have a name and can capture values from their context The thing to keep in mind for the moment is that you already have an intuition about closures. They are almost the same as functions but don’t necessarily have a name. // a closure that has no parameters and return a String var hello: () -> ( String ) = { ...

    SQLite 3 Procedure & Functions

    SQLite 3 Procedure & Functions iOS - SQLite Database SQLite can be used in iOS for handling data. It uses sqlite queries, which makes it easier for those who know SQL. Steps Involved Step 1.  Create a simple  View based application . Step 2.  Select your project file, then select targets and then add  libsqlite3.dylib  library in choose frameworks. Step 3.  Create a new file by selecting File-> New -> File... -> select  Objective C class  and click next. Step 4.  Name the class as  DBManager  with  "sub class of"  as NSObject. Step 5.  Select create. Step 6.  Update  DBManager.h  as follows − SQLite 3 Functions Preview sqlite3_open : This function is used to create and open a database file. It accepts two parameters, where the first one is the database file name, and the second a handler to the database. If the file does n...