Swift: Subclassing UITextView or UICollectionView and proper initialization

i0S Swift Issue

Question or problem in the Swift programming language:

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?

How to solve the problem:

Solution 1:

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

        // ...
    }
}

Solution 2:

A more complete answer for Swift 3:

class ValidatedTextField:UITextField, UITextFieldDelegate
{
    required override init(frame: CGRect)
    {
        super.init(frame: frame)
        //custom code
        self.delegate = self
    }

    required init?(coder: NSCoder)
    {
        super.init(coder: coder)
        //custom code
        self.delegate = self
    }

Hope this helps!