Doug Smith
Doug Smith

Reputation: 29316

How do I write a custom init for a UIView subclass in Swift?

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?

Upvotes: 162

Views: 155428

Answers (6)

Fattie
Fattie

Reputation: 12582

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)
    }
}

Upvotes: 0

Adhokshaja Madhwaraj
Adhokshaja Madhwaraj

Reputation: 598

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
    }
    
    
}


Upvotes: 33

MH175
MH175

Reputation: 2324

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
      }

}

Upvotes: 55

J-Dizzle
J-Dizzle

Reputation: 5143

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:

Upvotes: 19

J-Dizzle
J-Dizzle

Reputation: 5143

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"); }
}

Upvotes: 12

Wolf McNally
Wolf McNally

Reputation: 3657

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)
    }
}

Upvotes: 235

Related Questions