Search code examples
iosswiftuikit

How to initialize a subclass when no superclass init fits


I know this is a silly example, but it's the smallest one I could think of that demonstrates my problems.
Let's say I have a Swift 5 project with some UI Buttons in it. As background, this project doesn't use storyboards, and where possible I don't specify frames for subviews, as everything is positioned with the various .constraint() methods. Also, let's say all of my buttons are declared in one of these two ways:

let yellowButton = UIButton()
yellowButton.setTitleColor(.yellow, for: .normal)

let blueButton = UIButton()
blueButton.setTitleColor(.blue, for: .normal)

I decide that I want to compact this, by creating a subclass of UIButton that I can construct as let yellowButton = MyButton(isYellow: true), since there are only two colors. I've tried to write something like this:

import UIKit

class MyButton: UIButton {
  private var isYellow: Bool

  required init?(coder: NSCoder) {
    fatalError("Not using storyboards")
  }

  init(isYellow: Bool) {
    self.isYellow = isYellow
    super.init() // <-- Compile error here
  }

  // ... code to handle color automatically ...
}

However, it doesn't let me call super.init(). It gives me the options of super.init(coder:), super.init(frame:), super.init(type:), super.init(frame:primaryAction:), and super.init(type:primaryAction:). None of these seem particularly like what I want. Since I'm using constraints to position it, I suppose I could call init(frame:) with an arbitrary constant CGRect, but semantically that seems odd. I have two questions about this:

  1. Why can I call UIButton() in my view controllers, but not super.init() in a subclass of UIButton?
  2. What's the best way to handle this situation, and more generally, what should I do when none of the superclass's initializers seem appropriate?

Solution

  • The problem you're having calling super.init() is because that initializer is actually a convenience initializer, and subclasses must call a designated initializer. Most UIKit/AppKit classes that have both a designated initializer that accepts an NSRect and a convenience initializer with no parameters simply chain to the designated one with .zero for the NSRect. Paulw11's comments allude to this when he suggests calling it explicitly with .zero.