3

I have written a UIStackView subclass, but I am experiencing a strange run-time problem. Here is some sample code where it can be seen:

class SubclassedStackView: UIStackView {

    init(text: String, subtext: String) {
        let textlabel = UILabel()
        let subtextLabel = UILabel()
        textlabel.text = text
        subtextLabel.text = subtext
        super.init(arrangedSubviews: [textlabel, subtextLabel])
    }

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

If you then use it such as this:

let stackView = SubclassedStackView(text: "Test", subtext: "Uh-oh!")

You get a runtime exception with the following message:

fatal error: use of unimplemented initializer 'init(frame:)' for class 'test.SubclassedStackView'

A look at the call stack shows that the base initializer -[UIStackView initWithArrangedSubviews:] is attempting to call init(frame: CGRect) on the subclass, which is intentionally left unimplemented.

Of course I could implement this extra initializer, weird as it would be for it to be called by the superclass, but in my real-life case this would force me to change my properties to use optional types (or implicitly unwrapped optionals) where I shouldn't have to do that.

I could also call init(frame:) instead of init(arrangedSubviews:) and subsequently call addArrangedSubview(view:) to add the arranged subviews. The run-time issue would disappear, but I don't wish to provide a frame.

Why does the superclass's initializer call the subclass's initializer? Can anyone suggest a way to work around this issue without introducing optionals?

Edit: Apple acknowledged this bug which should be fixed in iOS 10. http://www.openradar.me/radar?id=4989179939258368 Still applies to iOS 8-9 unfortunately.

Clafou
  • 15,250
  • 7
  • 58
  • 89
  • This is similar to this issue (fixed in iOS9) with UITableViewController subclasses: http://stackoverflow.com/questions/25139494/how-to-subclass-uitableviewcontroller-in-swift/30719434?s=2|0.1651#30719434 – Clafou Apr 10 '16 at 14:28
  • The code above no longer compiles in iOS 10: Must call a designated initializer of the superclass 'UIStackView' – José Jul 06 '17 at 07:40

3 Answers3

3

I'm not sure if this will work for your needs, but I've managed to circumvent the problem with an extension on UIStackView:

extension UIStackView {
    convenience init(text: String, subtext: String) {
        let textlabel = UILabel()
        let subtextLabel = UILabel()
        textlabel.text = text
        subtextLabel.text = subtext
        self.init(arrangedSubviews: [textlabel, subtextLabel])
    }
}

// ...

let sv = UIStackView(text: "", subtext: "") // <UIStackView: 0x7fcd32022c20; frame = (0 0; 0 0); layer = <CATransformLayer: 0x7fcd32030810>>
JAL
  • 41,701
  • 23
  • 172
  • 300
  • Great for the example given, but in my actual case I need to add properties, so I do have to subclass! – Clafou Apr 08 '16 at 23:09
2

A look at the call stack shows that the base initialiser -[UIStackView initWithArrangedSubviews:] is attempting to call init(frame: CGRect) on the subclass, which is intentionally left unimplemented.

Why not just add the missing constructor?

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

The problem's coming from the frame initializer not being available since you've provided your own, and it needs that for its internal implementation. If the frame will be managed by AutoLayout anyways you don't need to be concerned with what it's actually set to initially, so you can just let it perform its internal routines necessary to initialize with your subviews. I don't see from the code above why you would need to add optionals..

Eric G
  • 91
  • 3
  • I see no problem in the base class calling another initialiser on itself, that's very common. But for it to call it on a subclass (traveling back up in the hierarchy after traveling down) doesn't seem right and introduces problems with Swift's initialisation rules which mandates that non-optional properties are initialised before the call to base init. My actual code (the one I gave was just a sample to show the runtime error) has non-optional let properties. – Clafou Apr 08 '16 at 23:21
0

init(arrangedSubviews: [] is a convenience initializer. As per documentation, you must call the superclass's designated initializer (which is init(frame:)) instead

Luong Huy Duc
  • 458
  • 7
  • 17