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

133,017

Solution 1

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

Solution 2

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
      }

}

Solution 3

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


Solution 4

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:

Solution 5

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"); }
}
Share:
133,017

Related videos on Youtube

Doug Smith
Author by

Doug Smith

I'm a web designer playing around with iOS from England

Updated on July 08, 2022

Comments

  • Doug Smith
    Doug Smith almost 2 years

    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?

  • Wolf McNally
    Wolf McNally almost 10 years
    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.
  • Decade Moon
    Decade Moon over 9 years
    This code doesn't compile. You need to implement the required initialiser init(coder:).
  • RaptoX
    RaptoX almost 8 years
    this would use the whole display for the View
  • J-Dizzle
    J-Dizzle almost 8 years
    yep! If interested in a partial subview, let me know and I'll post this as well
  • Carter Medlin
    Carter Medlin almost 8 years
    I like this answer the best, because having the fatalError means I don't have to put any code in the required init.
  • Ari Lacenski
    Ari Lacenski over 7 years
    @J-Dizzle, I'd like to see the solution for partial views.
  • mafiOSo
    mafiOSo about 7 years
    interesting how this compiled years ago. Nowadays it complains under init(coder:) that "Property self.s not initialized at super.init call"
  • Wolf McNally
    Wolf McNally about 7 years
    Fixed example for Swift 3.1. Compiles under a playground importing UIKit.
  • mfaani
    mfaani about 7 years
    isn't your answer saying the opposite of the accepted answer? I mean you're doing the customization after super.init, but he said it should be done before super.init...
  • LightNight
    LightNight almost 7 years
    super.init() must be called before initializing self params?
  • surfrider
    surfrider almost 7 years
    It shouldn't work: Use of 'self' in method call 'commonInit' before super.init initializes self
  • MH175
    MH175 almost 7 years
    Initialize custom arguments after self.init call. Updated my answer.
  • surfrider
    surfrider almost 7 years
    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.
  • Wolf McNally
    Wolf McNally almost 7 years
    @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().
  • Mike Critchley
    Mike Critchley over 6 years
    Nice use of the fatalError() call. I was having to use optionals just to silence warnings from an initializer that wasn't even being used. This shut it right up! Thanks.
  • J-Dizzle
    J-Dizzle over 6 years
    No, OP states "it tells me that "super.init() isn't called before returning from initializer", i.e. customization can happen at any time within init(). Also to note, you cannot modify 'self' before calling super.init() so your contest is not valid. Interesting consideration though, was fun to poke and explore!
  • J-Dizzle
    J-Dizzle over 6 years
    @Ari, For partial views see popupView, of height 35
  • ThomasW
    ThomasW over 6 years
    There is no need for that return or the semicolons.
  • MH175
    MH175 almost 6 years
    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
  • GranolaGuy
    GranolaGuy over 5 years
    Can someone explain why it's okay to not give initial values to self.i and self.s in int?(coder)? I know that it compiles because swift options. Are we expected to initialize them elsewhere? Where? Why can't we in int?(coder) like with the other inits?
  • Wolf McNally
    Wolf McNally over 5 years
    @GranolaGuy A bit off-topic, but the answer is that optional values have a default value of nil if you don't provide a specific one yourself.
  • Jordan H
    Jordan H over 3 years
    You can also add private init() { fatalError() } to make sure your custom init must be used
  • famfamfam
    famfamfam over 3 years
    this will cause crash if uiview is place into storyboard, which called init with coder