161

Say I want to init a UIView subclass with a String and an Int.

How would I do this in Swift if I'm just subclassing UIView? If I just make a custom init() function but the parameters are a String and an Int, it tells me that "super.init() isn't called before returning from initializer".

And if I call super.init() I'm told I must use a designated initializer. What should I be using there? The frame version? The coder version? Both? Why?

6 Answers 6

234

The init(frame:) version is the default initializer. You must call it only after initializing your instance variables. If this view is being reconstituted from a Nib then your custom initializer will not be called, and instead the init?(coder:) version will be called. Since Swift now requires an implementation of the required init?(coder:), I have updated the example below and changed the let variable declarations to var and optional. In this case, you would initialize them in awakeFromNib() or at some later time.

class TestView : UIView {
    var s: String?
    var i: Int?
    init(s: String, i: Int) {
        self.s = s
        self.i = i
        super.init(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
}
Sign up to request clarification or add additional context in comments.

3 Comments

Then by all means make them var. But the default best practice in Swift is to declare variables let unless there is a reason to declare them var. There was no such reason to do so in my code example above, hence let.
super.init() must be called before initializing self params?
@LightNight I made s and i Optional to keep things simple here. If they were not optional, they would also need to be initialized in the required initializer. Making them optional means they get to be nil when super.init() is called. If they weren't optional, they would indeed need to be assigned before calling super.init().
55

I create a common init for the designated and required. For convenience inits I delegate to init(frame:) with frame of zero.

Having zero frame is not a problem because typically the view is inside a ViewController's view; your custom view will get a good, safe chance to layout its subviews when its superview calls layoutSubviews() or updateConstraints(). These two functions are called by the system recursively throughout the view hierarchy. You can use either updateContstraints() or layoutSubviews(). updateContstraints() is called first, then layoutSubviews(). In updateConstraints() make sure to call super last. In layoutSubviews(), call super first.

Here's what I do:

@IBDesignable
class MyView: UIView {

      convenience init(args: Whatever) {
          self.init(frame: CGRect.zero)
          //assign custom vars
      }

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

      required init?(coder aDecoder: NSCoder) {
           super.init(coder: aDecoder)
           commonInit()
      }

      override func prepareForInterfaceBuilder() {
           super.prepareForInterfaceBuilder()
           commonInit()
      }

      private func commonInit() {
           //custom initialization
      }

      override func updateConstraints() {
           //set subview constraints here
           super.updateConstraints()
      }

      override func layoutSubviews() {
           super.layoutSubviews()
           //manually set subview frames here
      }

}

2 Comments

But what if you want to initialise some properties in commonInit method, but you can't place it after super in this case because you should initialise all properties BEFORE super call. Lol it seems like dead loop.
This is how Swift initialization often works: search for "two-phase initialization". You can use implicitly unwrapped optionals, but I recommend against it. Your architecture, especially when dealing with views, should initialize all local properties. I've used this commonInit() method to hundreds of views now. It works
33

Swift 5 Solution

You can try out this implementation for running Swift 5 on XCode 11


class CustomView: UIView {

    var customParam: customType
    
    var container = UIView()
    
    required init(customParamArg: customType) {
        self.customParam = customParamArg
        super.init(frame: .zero)
        // Setting up the view can be done here
        setupView()
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    

    func setupView() {
        // Can do the setup of the view, including adding subviews

        setupConstraints()
    }
    
    func setupConstraints() {
        // setup custom constraints as you wish
    }
    
    
}


2 Comments

You can also add private init() { fatalError() } to make sure your custom init must be used
this will cause crash if uiview is place into storyboard, which called init with coder
19

Here is how I do it on iOS 9 in Swift -

import UIKit

class CustomView : UIView {

    init() {
        super.init(frame: UIScreen.mainScreen().bounds);

        //for debug validation
        self.backgroundColor = UIColor.blueColor();
        print("My Custom Init");

        return;
    }

    required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented"); }
}

Here is a full project with example:

1 Comment

There is no need for that return or the semicolons.
12

Here is how I do a Subview on iOS in Swift -

class CustomSubview : UIView {

    init() {
        super.init(frame: UIScreen.mainScreen().bounds);

        let windowHeight : CGFloat = 150;
        let windowWidth  : CGFloat = 360;

        self.backgroundColor = UIColor.whiteColor();
        self.frame = CGRectMake(0, 0, windowWidth, windowHeight);
        self.center = CGPoint(x: UIScreen.mainScreen().bounds.width/2, y: 375);

        //for debug validation
        self.backgroundColor = UIColor.grayColor();
        print("My Custom Init");

        return;
    }

    required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented"); }
}

Comments

0

The very straightforward answer, 2024

class TemperatureGraph: UIView {
    
    var data: [Temperature]
    
    required init(data: [Temperature]) {
        self.data = data
        super.init(frame: .zero)
    }
    
    required init?(coder aDecoder: NSCoder) {
        self.data = []
        super.init(coder: aDecoder)
    }
}

another example

class TemperatureGraph: UIView {
    
    var data: YourDataClass
    
    required init(data: YourDataClass) {
        self.data = data
        super.init(frame: .zero)
    }
    
    required init?(coder aDecoder: NSCoder) {
        self.data = YourDataClass()
        super.init(coder: aDecoder)
    }
}

another example

class TemperatureGraph: UIView {
    
    var height: Int
    var color: UIColor
    
    required init(height: Int, color: UIColor) {
        self.height = height
        self.color = color
        super.init(frame: .zero)
    }
    
    required init?(coder aDecoder: NSCoder) {
        self.height = 180
        self.color = .blue
        super.init(coder: aDecoder)
    }
}

The formula is

  1. in required init, set the properties you added, then call super

  2. in the coder init, set the properties you added to whatever defaults you prefer, then call super

That's it.

Note that you don't have to pass in the values, as long as you initialize them (in the two calls) before calling super.

For example

class Countdown: UIView {
    
    var seconds: Int

    required init() {
        self.seconds = 10
        super.init(frame: .zero)
    }
    
    required init?(coder aDecoder: NSCoder) {
        self.seconds = 10
        super.init(coder: aDecoder)
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.