Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift: Subclassing UITextView or UICollectionView and proper initialization

The problem with subclassing UITextView (and UICollectionView) is that designated constructor is "initWithFrame". But in real life, when it loads from storyboard, initWithCoder will be called.

class BorderedTextView: UITextView {
    //will be called
    init(coder: NSCoder?){
       //constant values here is not an option
       super.init(frame: CGRectMake(0,0,100,100), textContainer: nil)
    }
    //will not be called
    init(frame: CGRect, textContainer: NSTextContainer!) {
        super.init(frame: frame, textContainer: textContainer)
    }
}

As result I cannot call any UI customisation code on init and provide any initialization value for Swift variables except defaults.

I suppose that problem can be temporary solved by extracting frame size from "coder", but I didn't found the key for it.

Any ideas better than hardcode frame values?

like image 732
Alexey Suvorov Avatar asked Jun 29 '14 14:06

Alexey Suvorov


1 Answers

(From my above comments:) This looks like a Swift bug. initWithCoder: is called when a view (or view controller) is instantiated from a Storyboard or Nib file, and overriding that method works in Objective-C:

- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
    self = [super initWithCoder:aDecoder];
    if (self) {
        // Initialization code
    }
    return self;
}

But the equivalent Swift code

class BorderedTextView: UITextView {
    init(coder: NSCoder!) {
        super.init(coder: coder)
    }
}

fails with the error message "must call a designated initializer of the superclass 'UITextView'".

This problem occurs with all subclasses of UIView that have a their own designated initializer (e.g. UITextView, UICollectionView). On the other hand, the problem does not occur with subclasses of UILabel, which does not have a designated initializer. The Swift language is very strict about calling the super classes' designated initializer, but there should be a way to override initWithCoder: for all custom UIView subclasses, so I consider this a Swift bug.

As a workaround, you can do the custom initialisation in

override func awakeFromNib() {
    super.awakeFromNib()
    // ...
}

Update for Swift 1.2: This apparently has been fixed. The parameter changed, it is no longer an implicitly unwrapped optional. So this compiles and works as expected (tested with Xcode 6.4):

class BorderedTextView: UITextView {
    required init(coder: NSCoder) {
        super.init(coder: coder)

        // ...
    }
}

Update for Swift 2 (Xcode 7): init(coder:) is a failable initializer now:

class BorderedTextView: UITextView {
    required init?(coder: NSCoder) {
        super.init(coder: coder)

        // ...
    }
}
like image 179
Martin R Avatar answered Oct 26 '22 23:10

Martin R