UIView's designated initializers in swift - ios

So I've subclassed UIView like so
class CustomView: UIView {
}
And I could do something like
let customView = CustomView()
But when I override what I believe are the two designated initialisers for UIView i.e. init(frame:) and init(coder:) like so
class CustomView: UIView {
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.backgroundColor = UIColor.redColor()
}
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.redColor()
}
}
I can no longer create the view like so as it complains that init() no longer exists
let customView = CustomView()
My understanding is that If I override all the designated initialisers, I should inherit all the convenience initializers as well.
So my question is. Is init() not a convenience initializer on UIView? Or is there another designated initializer on UIView that I haven't overrided yet?

init is not a convenience initializer on UIView. init is not an initializer on UIView at all! It is an initializer on its superclass NSObject - UIView merely inherits it. And it is a designated initializer on NSObject. So when you implemented a designated initializer, you cut off inheritance of designated initializers - including init.

Related

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")
}
}

Cannot subclass UIButton: Must call a designated initializer of the superclass 'UIButton'

Trying to subclass UIButton but the error Must call a designated initializer of the superclass 'UIButton' occurs.
Researching several SO posts like this, this, this, or several others did not help as those solutions didn't work.
How can we subclass UIButton in Swift and define a custom init function?
import UIKit
class KeyboardButton : UIButton {
var letter = ""
var viewController:CustomViewController?
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
init(letter: String, viewController: CustomViewController) {
super.init()
...
}
}
You have to call the superclass' designated initializer:
Swift 3 & 4:
init(letter: String, viewController: CustomViewController) {
super.init(frame: .zero)
}
Swift 1 & 2:
init(letter: String, viewController: CustomViewController) {
super.init(frame: CGRectZero)
}
As Paulw11 says in the comments, a view generally shouldn't have a reference to its controller, except as a weak reference using the delegate pattern, which would promote reusability.

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.

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?
(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
}

Resources