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.
Related
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.
I have a simple UIView subclass that looks like this:
import UIKit
class AngleViewManager: UIView {
class AngleView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.red
}
init(first: CGPoint, second: CGPoint, third: CGPoint) {
// setup
super.init(frame: CGRect(dimensions))
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
}
When I initialize an AngleView instance within AngleViewManager like
AngleView(firstPoint, secondPoint, thirdPoint),
the background color is not red, and setting a breakpoint in the overridden init shows that it is never called even though I am explicitly calling it in my custom initializer which does get called successfully.
Am I missing something obvious?
You're calling the super class's (UIView) implementation of init(frame: CGRect).
Just change it to self.init(frame: frame)
The following code returns a couple of compiler errors after converting to swift3:
override init(frame: CGRect) { //Initializer does not override a designated initializer from its superclass
super.init(frame: frame) //Must call a designated initializer of the superclass 'MKAnnotationView'
}
How do I go about fixing this?
I am guessing (from the comment in your code) that you are trying to create a subclass of MKAnnotationView. If thats true, try this.
class myAnnot : MKAnnotationView{
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
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")
}
}
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
}