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?

mfaani
  • 33,269
  • 19
  • 164
  • 293
Doug Smith
  • 29,668
  • 57
  • 204
  • 388

5 Answers5

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)
    }
}
Wolf McNally
  • 3,647
  • 1
  • 16
  • 13
  • 5
    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`. – Wolf McNally Jun 24 '14 at 14:27
  • 2
    This code doesn't compile. You need to implement the required initialiser `init(coder:)`. – Decade Moon Nov 29 '14 at 00:24
  • 4
    interesting how this compiled years ago. Nowadays it complains under init(coder:) that "Property self.s not initialized at super.init call" – mafiOSo Apr 11 '17 at 06:20
  • Fixed example for Swift 3.1. Compiles under a playground importing UIKit. – Wolf McNally Apr 13 '17 at 20:41
  • `super.init()` must be called before initializing self params? – LightNight Aug 06 '17 at 11:31
  • 1
    @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(). – Wolf McNally Aug 24 '17 at 18:33
  • 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? – GranolaGuy Feb 05 '19 at 15:39
  • @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. – Wolf McNally Feb 08 '19 at 09:41
54

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
      }

}
MH175
  • 2,234
  • 1
  • 19
  • 35
  • 3
    It shouldn't work: Use of 'self' in method call 'commonInit' before super.init initializes self – surfrider Aug 11 '17 at 11:13
  • 1
    Initialize custom arguments after self.init call. Updated my answer. – MH175 Aug 11 '17 at 14:40
  • 1
    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. – surfrider Aug 11 '17 at 16:11
  • 1
    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 – MH175 Jul 21 '18 at 19:48
32

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


18

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:

J-Dizzle
  • 4,861
  • 4
  • 40
  • 50
  • 3
    this would use the whole display for the View – RaptoX Aug 04 '16 at 14:03
  • 1
    yep! If interested in a partial subview, let me know and I'll post this as well – J-Dizzle Aug 05 '16 at 17:51
  • 1
    I like this answer the best, because having the fatalError means I don't have to put any code in the required init. – Carter Medlin Aug 16 '16 at 21:15
  • 1
    @J-Dizzle, I'd like to see the solution for partial views. – Ari Lacenski Sep 29 '16 at 02:17
  • 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`... – mfaani May 16 '17 at 20:43
  • 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 Dec 18 '17 at 22:10
  • @Ari, For partial views see popupView, of height 35 – J-Dizzle Dec 18 '17 at 22:41
  • There is no need for that `return` or the semicolons. – ThomasW Feb 16 '18 at 02:15
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"); }
}
J-Dizzle
  • 4,861
  • 4
  • 40
  • 50
  • 4
    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. – Mike Critchley Dec 17 '17 at 13:21