50

Assume a class that is derived from UIView as follows:

class MyView: UIView {
    var myImageView: UIImageView

    init(frame: CGRect) {
        super.init(frame: frame)
    }

    init(coder aDecoder: NSCoder!) {
        super.init(coder: aDecoder)
    }

    ...

If I wanted to have the same code in both of the initializers, like

self.myImageView = UIImageView(frame: CGRectZero)
self.myImageView.contentMode = UIViewContentMode.ScaleAspectFill

and NOT duplicate that code twice in the class implementation, how would I structure the init methods?

Tried approaches:

  • Created a method func commonInit() that is called after super.init -> Swift compiler gives an error about an uninitialized variable myImageView before calling super.init
  • Calling func commonInit() before super.init fails self-evidently with a compiler error "'self' used before super.init call"
Markus Rautopuro
  • 7,997
  • 6
  • 47
  • 60

7 Answers7

23

What we need is a common place to put our initialization code before calling any superclass's initializers, so what I currently using, shown in a code below. (It also cover the case of interdependence among defaults and keep them constant.)

import UIKit

class MyView: UIView {
        let value1: Int
        let value2: Int

        enum InitMethod {
                case coder(NSCoder)
                case frame(CGRect)
        }

        override convenience init(frame: CGRect) {
                self.init(.frame(frame))!
        }

        required convenience init?(coder aDecoder: NSCoder) {
                self.init(.coder(aDecoder))
        }

        private init?(_ initMethod: InitMethod) {
                value1 = 1
                value2 = value1 * 2 //interdependence among defaults

                switch initMethod {
                case let .coder(coder): super.init(coder: coder)
                case let .frame(frame): super.init(frame: frame)
                }
        }
}
Joren
  • 14,472
  • 3
  • 50
  • 54
linimin
  • 6,239
  • 2
  • 26
  • 31
  • 1
    Very nice and clean. Only solution that preserves the `let` properties and keeps the property initialization and further setup together. You could clean it up further to better illustrate the solution by dropping `myImageView` and `InitMethod.Default`, and moving `InitMethod` into `MyView`. – Glen Low Mar 15 '16 at 19:21
  • No optional / IUO / var. This is brilliant. – Jilouc Aug 27 '18 at 09:16
  • Nice! But it's not working on Xcode 10.2 due to [this issue](https://stackoverflow.com/a/26772103/2396171). I've added another answer that includes a workaround. – jedwidz Aug 02 '20 at 07:40
  • Aside from the Xcode 10.2 issue, `enum InitMethod` could be marked as `private`. – jedwidz Aug 02 '20 at 07:41
21

I just had the same problem.

As GoZoner said, marking your variables as optional will work. It's not a very elegant way because you then have to unwrap the value each time you want to access it.

I will file an enhancement request with Apple, maybe we could get something like a "beforeInit" method that is called before every init where we can assign the variables so we don't have to use optional vars.

Until then, I will just put all assignments into a commonInit method which is called from the dedicated initialisers. E.g.:

class GradientView: UIView {
    var gradientLayer: CAGradientLayer?  // marked as optional, so it does not have to be assigned before super.init
    
    func commonInit() {
        gradientLayer = CAGradientLayer()
        gradientLayer!.frame = self.bounds
        // more setup 
    }
     
    init(coder aDecoder: NSCoder!)  {
        super.init(coder: aDecoder)
        commonInit()
    }
    
     init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        gradientLayer!.frame = self.bounds  // unwrap explicitly because the var is marked optional
    }
}

Thanks to David I had a look at the book again and I found something which might be helpful for our deduplication efforts without having to use the optional variable hack. One can use a closure to initialize a variable.

Setting a Default Property Value with a Closure or Function

If a stored property’s default value requires some customization or setup, you can use a closure or global function to provide a customized default value for that property. Whenever a new instance of the type that the property belongs to is initialized, the closure or function is called, and its return value is assigned as the property’s default value. These kinds of closures or functions typically create a temporary value of the same type as the property, tailor that value to represent the desired initial state, and then return that temporary value to be used as the property’s default value.

Here’s a skeleton outline of how a closure can be used to provide a default property value:

class SomeClass {
let someProperty: SomeType = {
    // create a default value for someProperty inside this closure
    // someValue must be of the same type as SomeType
    return someValue
    }() 
}

Note that the closure’s end curly brace is followed by an empty pair of parentheses. This tells Swift to execute the closure immediately. If you omit these parentheses, you are trying to assign the closure itself to the property, and not the return value of the closure.

NOTE

If you use a closure to initialize a property, remember that the rest of the instance has not yet been initialized at the point that the closure is executed. This means that you cannot access any other property values from within your closure, even if those properties have default values. You also cannot use the implicit self property, or call any of the instance’s methods.

Excerpt From: Apple Inc. “The Swift Programming Language.” iBooks. https://itun.es/de/jEUH0.l

This is the way I will use from now on, because it does not circumvent the useful feature of not allowing nil on variables. For my example it'll look like this:

class GradientView: UIView {
    var gradientLayer: CAGradientLayer = {
        return CAGradientLayer()
    }()
    
    func commonInit() {
        gradientLayer.frame = self.bounds
        /* more setup */
    }
    
    init(coder aDecoder: NSCoder!)  {
        super.init(coder: aDecoder)
        commonInit()
    }
    
    init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
}
Community
  • 1
  • 1
Matthias Bauch
  • 89,811
  • 20
  • 225
  • 247
  • So if I understand your excellent explanation you can omit having the @lazy keyword when initialization is done by using closures? – Konrad77 Jun 15 '14 at 15:13
  • 3
    The closures combined with `commonInit()` are exactly what I was looking for, thank you. Or just use the shorter form: `var gradientLayer: CAGradientLayer = CAGradientLayer()` (or even shorter `var gradientLayer = CAGradientLayer()`) if you just want to allocate the object. – Markus Rautopuro Sep 22 '14 at 16:08
3

How about this?

public class MyView : UIView
{
    var myImageView: UIImageView = UIImageView()

    private func setup()
    {
        myImageView.contentMode = UIViewContentMode.ScaleAspectFill
    }

    override public init(frame: CGRect)
    {
        super.init(frame: frame)
        setup()
    }

    required public init(coder aDecoder: NSCoder)
    {
        super.init(coder: aDecoder)
        setup()
    }
}
Skela
  • 884
  • 10
  • 18
  • Yes, that'll also do. It seems that a newer Xcode beta has introduced access control (https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AccessControl.html). – Markus Rautopuro Sep 22 '14 at 16:12
  • I've added what I consider the most likely required access control for the example at hand. – Skela Sep 22 '14 at 16:19
  • Yes, I have the GM now - I just realized they added the Access Controls in beta 4. :) – Markus Rautopuro Sep 22 '14 at 16:28
1

Assign myImageView in both the init() methods based on a single image create function. As such:

self.myImageView = self.createMyImageView ();

For example, like such:

class Bar : Foo {
    var x : Int?
    func createX () -> Int { return 1 }
    init () {
        super.init ()
        self.x = self.createX ()
    }   
}

Note the 'optional' use at Int?

pkamb
  • 33,281
  • 23
  • 160
  • 191
GoZoner
  • 67,920
  • 20
  • 95
  • 145
  • The optional is the important part. Assigning the value via an method is superfluous. `self.x = 1` will work as well. – Matthias Bauch Jun 05 '14 at 03:41
  • The point was to define a function `createX()` that would do complex stuff needed by multiple `init()` methods; the example did only the most simplistic thing - return `1`. – GoZoner Jun 05 '14 at 15:44
  • I'll mark Matthias Bauch's answer correct, because marking variables optional requires unwrapping the value every time you want to access it. – Markus Rautopuro Sep 22 '14 at 16:06
1

Does it necessarily have to come before? I think this is one of the things implicitly unwrapped optionals can be used for:

class MyView: UIView {
    var myImageView: UIImageView!

    init(frame: CGRect) {
        super.init(frame: frame)
        self.commonInit()
    }

    init(coder aDecoder: NSCoder!) {
        super.init(coder: aDecoder)
        self.commonInit()
    }

    func commonInit() {
        self.myImageView = UIImageView(frame: CGRectZero)
        self.myImageView.contentMode = UIViewContentMode.ScaleAspectFill
    }

    ...
}

Implicitly unwrapped optionals allow you skip variable assignment before you call super. However, you can still access them like normal variables:

var image: UIImageView = self.myImageView // no error

Archagon
  • 2,470
  • 2
  • 25
  • 38
  • 2
    Unfortunately the implicit unwrapped optional kills the benefit of not being able to assign nil or an optional to a variable that shouldn't be optional in the first place. – Matthias Bauch Sep 14 '14 at 09:16
  • Hmm, good point. But IIRC, something like this was one of the examples given in the Swift book. – Archagon Sep 14 '14 at 23:11
  • Also IUO's can lead to unexpected crashes if the code evolves and someone forgets to initialize the property. – Cristik Dec 29 '17 at 13:16
1

Yet another option using a static method (added 'otherView' to highlight scalability)

class MyView: UIView {

    var myImageView: UIImageView
    var otherView: UIView

    override init(frame: CGRect) {
        (myImageView,otherView) = MyView.commonInit()
        super.init(frame: frame)
    }

    required init(coder aDecoder: NSCoder) {
        (myImageView, otherView) = MyView.commonInit()
        super.init(coder: aDecoder)!
    }

    private static func commonInit() -> (UIImageView, UIView) {
        //do whatever initialization stuff is required here
        let someImageView = UIImageView(frame: CGRectZero)
        someImageView.contentMode = UIViewContentMode.ScaleAspectFill
        let someView = UIView(frame: CGRect(x: 0, y: 0, width: 30, height: 30))
        return (someImageView, someView)
    }
}
PakitoV
  • 2,476
  • 25
  • 34
0

Additionally, if the intention is to assign myImageView exactly once, it should be a let rather than a var. That rules out some solutions that only work for var.

Another complication is multiple instance variables with dependencies between them. This rules out inline initializers calling static methods.

These requirements can be addressed by overriding with convenience initializers, which delegate to a single designated initializer:

import UIKit

class MyView: UIView {
    let myImageView: UIImageView
    // Just to illustrate dependencies...
    let myContainerView: UIView
    
    override convenience init(frame: CGRect) {
        self.init(frame: frame, coder: nil)!
    }
    
    required convenience init?(coder aDecoder: NSCoder) {
        // Dummy value for `frame`
        self.init(frame: CGRect(), coder: aDecoder)
    }
    
    @objc private init?(frame: CGRect, coder aDecoder: NSCoder?) {
        // All `let`s must be assigned before
        // calling `super.init`...
        myImageView = UIImageView(frame: CGRect.zero)
        myImageView.contentMode = .scaleAspectFill
        
        // Just to illustrate dependencies...
        myContainerView = UIView()
        myContainerView.addSubview(myImageView)
        
        if let aDecoderNonNil = aDecoder {
            super.init(coder: aDecoderNonNil)
        } else {
            super.init(frame: frame)
        }
        
        // After calling `super.init`, can safely reference
        // `self` for more common setup...
        self.someMethod()
    }

    ...    
}

This is based on ylin0x81's answer, which I really like but doesn't work now (build with Xcode 10.2), as load from nib crashes with:

This coder requires that replaced objects be returned from initWithCoder:

This issue is covered on a separate question, with iuriimoz's answer suggesting to add @objc to the designated initializer. That entailed avoiding the Swift-only enum used by ylin0x81.

jedwidz
  • 384
  • 5
  • 7