Swift: Subclassing UITextView or UICollectionView and proper initialization - ios

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?

(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)
// ...
}
}

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
}

Related

Why does calling init() calls init(frame: CGRect)?

When I have a custom UIView with overridden init like this:
class ContainerView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setup() {
}
}
Why am I able to initialize the view with a simple init like this and the setup() method is called?
let view = CustomView()
I would expect it to NOT be called when I'm calling the simple NSObject init() and not the init(frame: CGRect)?
How is the frame parameter passed then?
Calling CustomView() is using an inherited initializer for NSObject. This implicitly calls UIView's designated initializer, init(frame:) with .zero. Since you've overriden that initializer, your override is called and thus the setup() method is called too.
You can see this by printing out the frame parameter in your initializer; you will get a .zero rect.

Why don't we have to use "frame" when declaring a custom view?

I don't get why when I create this view:
class MenuBar: UIView {
override init (frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.blue
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
...and when I using it in another class, I can put some code like this:
let menuBar: MenuBar = {
let mb = MenuBar()
return mb
}()
Why don't I have to mention anything about frame?
UIView has a default initializer which has no arguments. Regardless of subclassing UIView or not, that initializer is available. It initalizes a UIView instance with a frame of CGRect.zero.
You are calling a convenience initializer inherited from the UIView implementation. This parameterless convenience initializer calls the one with the frame and passes it a default value - in the end, your initializer with frame is called from the inherited superclass initializer.

Override init method of UIView in swift

class CustomView: UIView {
var subViewColor:UIColor
var subViewMessage:String
override init(frame:CGRect) {
super.init(frame:frame)
}
init(subViewColor:UIColor,subViewMessage:String){
self.subViewColor = subViewColor
self.subViewMessage = subViewMessage
super.init()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I have a class where I want the user to initialize a custom view either by giving properties like:
let myView = CustomLoadingView(initialize properties here)
If the user does not want to initialize their own properties, I want to initialize CustomLoadingView using default properties...
let myView = CustomLoadingView() // this should initialize using default value
However, with this, I am getting this error:
Must call a designated intializer of the superclass UIView
In init(subviewColor: UIColor, subViewMessage: String), you aren't calling the designated initializer (as the compiler points out nicely).
If you don't know what designated initializers are, they are initializers that have to be called by the subclass at some point. From the docs:
Designated initializers are the primary initializers for a class. A designated initializer fully initializes all properties introduced by that class and calls an appropriate superclass initializer to continue the initialization process up the superclass chain.
In this case, the designated initializer for UIView is init(frame: CGRect), meaning at some point, your new initializer init(subviewColor: UIColor, subViewMessage: String must call super.init(frame:).
In order to fix this, make the following changes:
init(frame: CGRect, subViewColor: UIColor, subViewMessage: String){
self.subViewColor = subViewColor
self.subViewMessage = subViewMessage
super.init(frame: frame)
}
OR you can call your other initializer in your class which ends up calling the designated initializer.
override init(frame: CGRect) {
super.init(frame: frame) // calls designated initializer
}
convenience init(frame: CGRect, subViewColor: UIColor, subViewMessage: String){
self.subViewColor = subViewColor
self.subViewMessage = subViewMessage
self.init(frame: frame) // calls the initializer above
}
As for the convenience method with simply CustomLoadingView(), you have to add another initializer for that. Add this code to your custom view:
convenience init() {
self.init(frame: DEFAULT_FRAME, subViewColor: DEFAULT_COLOR, subViewMessage: DEFAULT_MESSAGE)
}
If you want to learn more about designated and convenience initializers, read about them here and here.
You must call one of UIView's designated initializers at some point in your custom initializer, for instance: super.init(frame: frameX). Your call of super.init() does not satisfy this requirement.
Foe me it was calling a xib that doesn't exist from custom view initialiser in
Bundle.main.loadNibNamed("XibNameThatDoesntExist", owner: self, options: nil)
`
Try this:
class CustomView: UIView {
var subViewColor:UIColor
var subViewMessage:String
init(subViewColor:UIColor,subViewMessage:String){
self.subViewColor = subViewColor
self.subViewMessage = subViewMessage
let frame = self.frame
//Or you can use custom frame.
super.init(frame: frame)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

Super.init isn't called before returning from initializer

I try to give my UITableViewCell Class a custom initilizer but i can't figure it out what I am doing wrong.
Here is my Code:
init(dataObject: [NSManagedObject]!, objectAttributeValues: [String]!,placeholder: String!, segmentedControl: UISegmentedControl?, cellHeight: CGRect, cellWidth: CGRect) {
self.dataObject = dataObject
self.Placeholder.text = placeholder
self.objectAttributeValues = objectAttributeValues
if segmentedControl != nil {
self.segmentedControl = segmentedControl!
didHaveSegmentedControl = true
}
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
I tried to call super.init(frame: CGRect(...)) but by implementing this I get another error: Must call a designated initializer of the superclass 'UITableViewCell'
What can I do?
Thank you a lot!
The way initialisers work, is they will add their own properties, constants and functions to that instance, then call back to the superclass for an object of it's type. More info here.
For this reason you must call a superclass' initialiser before exiting the initialiser. Here I suggest you call super.init() on the last line of your initialiser. You can choose which of the init methods on UITableViewCell is most appropriate.
override init(frame: CGRect) {
super.init(frame: frame)
}
convenience init(frame: CGRect, dataObject: [NSManagedObject]!, objectAttributeValues: [String]!,placeholder: String!, segmentedControl: UISegmentedControl?, cellHeight: CGRect, cellWidth: CGRect){
self.init(frame: frame)
self.dataObject = dataObject
self.Placeholder.text = placeholder
self.objectAttributeValues = objectAttributeValues
if segmentedControl != nil {
self.segmentedControl = segmentedControl!
didHaveSegmentedControl = true
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
I hope this will help you Override init method of UIView in swift
You have to call the superclasses designated initializer.
For example, I tried to create a subclass of UIView and had the exact same problem you had.
The designated initializer for UIView is super.init(frame: CGRect)
For UITableViewCell the designated initializers are as follows.
// Designated initializer. If the cell can be reused, you must pass in a reuse
identifier. You should use the same reuse identifier for all cells of the same
form.
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:
(nullable NSString *)reuseIdentifier NS_AVAILABLE_IOS(3_0)
NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder
NS_DESIGNATED_INITIALIZER;
Hope this helped.

Handle UIView common initialization in swift

Some special classes like UIView have more than one designated initializer.
In Objective-X, we could factor common initialization into a separate function
- (id)initWithCoder:(NSCoder *)aDecoder {
if (self = [super initWithCoder:aDecoder]) {
[self initialize];
}
return self;
}
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[self initialize];
}
return self;
}
In Swift this is no longer a possibility because the following code results 'self used before super.init call'
override init(frame: CGRect) {
self.initialize()
super.init(frame: frame)
}
required init(coder aDecoder: NSCoder) {
self.initialize()
super.init(coder: aDecoder)
}
Placing self.initialize after super.init is no help, either, since my entire purpose is initializing members. The following would result in 'property self.X not initialized at super.init call'
var X : Int
override init(frame: CGRect) {
super.init(frame: frame)
self.initialize()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.initialize()
}
What should i do if i want to factor common initialization in this case? Note that i specifically do not want to use awakeFromNib because i want my objects to be available in any related awakeFromNib implementations in my object hierarchy. Note also that optionals do not make sense for my use case.
The obvious answer is to call initialize after calling super.init:
override init(frame: CGRect) {
super.init(frame: frame)
self.initialize()
}
However, you probably have some instance variables that you want to initialize in the initialize method. The facts are these:
You must initialize all member variables before calling super.
You may not call methods on self (or access computed properties on self) before calling super.init.
The inexorable conclusion is that if you have instance variables you want to initialize in initialize, you must declare each such variable with var, not let, and do one of these things:
Give the instance variable a default value:
var name: String = "Fred"
Make the instance variable Optional, and let it be initialized to nil:
var name: String?
Make the instance variable ImplicitlyUnwrappedOptional, and let it be initialized to nil:
var name: String!
My preference at this point is #2 (make it Optional and initialized to nil).
There's a neat solution to this here: https://stackoverflow.com/a/26772103/2420477
Basically, mark your two init functions convenience and then have them call out to a third private init function that has your common initialisation code.
For your case, you could use something like this:
class MyView: UIView {
let X: Int // No need to use an optional, as we assign this in an initializer
enum InitMethod {
case Coder(NSCoder)
case Frame(CGRect)
}
override convenience init(frame: CGRect) { // Marking as convenience let's us call out to another constructor
self.init(.Frame(frame))!
}
required convenience init?(coder aDecoder: NSCoder) { // Marking as convenience let's us call out to another constructor
self.init(.Coder(aDecoder))
}
private init?(_ initMethod: InitMethod) {
// You can put your common initialization code here:
X = 1
switch initMethod {
case let .Coder(coder): super.init(coder: coder)
case let .Frame(frame): super.init(frame: frame)
}
}
}

Resources