Override init method of UIView in swift - ios

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

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.

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.

UIView's designated initializers in swift

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.

How to implement two inits with same content without code duplication in Swift?

Assume a class that is derived from UIView as follows:
class MyView: UIView {
var myImageView: UIImageView
init(frame: CGRect) {
super.init(frame: frame)
}
init(coder aDecoder: NSCoder!) {
super.init(coder: aDecoder)
}
...
If I wanted to have the same code in both of the initializers, like
self.myImageView = UIImageView(frame: CGRectZero)
self.myImageView.contentMode = UIViewContentMode.ScaleAspectFill
and NOT duplicate that code twice in the class implementation, how would I structure the init methods?
Tried approaches:
Created a method func commonInit() that is called after super.init -> Swift compiler gives an error about an uninitialized variable myImageView before calling super.init
Calling func commonInit() before super.init fails self-evidently with a compiler error "'self' used before super.init call"
What we need is a common place to put our initialization code before calling any superclass's initializers, so what I currently using, shown in a code below. (It also cover the case of interdependence among defaults and keep them constant.)
import UIKit
class MyView: UIView {
let value1: Int
let value2: Int
enum InitMethod {
case coder(NSCoder)
case frame(CGRect)
}
override convenience init(frame: CGRect) {
self.init(.frame(frame))!
}
required convenience init?(coder aDecoder: NSCoder) {
self.init(.coder(aDecoder))
}
private init?(_ initMethod: InitMethod) {
value1 = 1
value2 = value1 * 2 //interdependence among defaults
switch initMethod {
case let .coder(coder): super.init(coder: coder)
case let .frame(frame): super.init(frame: frame)
}
}
}
I just had the same problem.
As GoZoner said, marking your variables as optional will work. It's not a very elegant way because you then have to unwrap the value each time you want to access it.
I will file an enhancement request with Apple, maybe we could get something like a "beforeInit" method that is called before every init where we can assign the variables so we don't have to use optional vars.
Until then, I will just put all assignments into a commonInit method which is called from the dedicated initialisers. E.g.:
class GradientView: UIView {
var gradientLayer: CAGradientLayer? // marked as optional, so it does not have to be assigned before super.init
func commonInit() {
gradientLayer = CAGradientLayer()
gradientLayer!.frame = self.bounds
// more setup
}
init(coder aDecoder: NSCoder!) {
super.init(coder: aDecoder)
commonInit()
}
init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
override func layoutSubviews() {
super.layoutSubviews()
gradientLayer!.frame = self.bounds // unwrap explicitly because the var is marked optional
}
}
Thanks to David I had a look at the book again and I found something which might be helpful for our deduplication efforts without having to use the optional variable hack. One can use a closure to initialize a variable.
Setting a Default Property Value with a Closure or Function
If a stored property’s default value requires some customization or setup, you can use a closure or global function to provide a customized default value for that property. Whenever a new instance of the type that the property belongs to is initialized, the closure or function is called, and its return value is assigned as the property’s default value. These kinds of closures or functions typically create a temporary value of the same type as the property, tailor that value to represent the desired initial state, and then return that temporary value to be used as the property’s default value.
Here’s a skeleton outline of how a closure can be used to provide a default property value:
class SomeClass {
let someProperty: SomeType = {
// create a default value for someProperty inside this closure
// someValue must be of the same type as SomeType
return someValue
}()
}
Note that the closure’s end curly brace is followed by an empty pair of parentheses. This tells Swift to execute the closure immediately. If you omit these parentheses, you are trying to assign the closure itself to the property, and not the return value of the closure.
NOTE
If you use a closure to initialize a property, remember that the rest of the instance has not yet been initialized at the point that the closure is executed. This means that you cannot access any other property values from within your closure, even if those properties have default values. You also cannot use the implicit self property, or call any of the instance’s methods.
Excerpt From: Apple Inc. “The Swift Programming Language.” iBooks. https://itun.es/de/jEUH0.l
This is the way I will use from now on, because it does not circumvent the useful feature of not allowing nil on variables. For my example it'll look like this:
class GradientView: UIView {
var gradientLayer: CAGradientLayer = {
return CAGradientLayer()
}()
func commonInit() {
gradientLayer.frame = self.bounds
/* more setup */
}
init(coder aDecoder: NSCoder!) {
super.init(coder: aDecoder)
commonInit()
}
init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
}
How about this?
public class MyView : UIView
{
var myImageView: UIImageView = UIImageView()
private func setup()
{
myImageView.contentMode = UIViewContentMode.ScaleAspectFill
}
override public init(frame: CGRect)
{
super.init(frame: frame)
setup()
}
required public init(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
setup()
}
}
Does it necessarily have to come before? I think this is one of the things implicitly unwrapped optionals can be used for:
class MyView: UIView {
var myImageView: UIImageView!
init(frame: CGRect) {
super.init(frame: frame)
self.commonInit()
}
init(coder aDecoder: NSCoder!) {
super.init(coder: aDecoder)
self.commonInit()
}
func commonInit() {
self.myImageView = UIImageView(frame: CGRectZero)
self.myImageView.contentMode = UIViewContentMode.ScaleAspectFill
}
...
}
Implicitly unwrapped optionals allow you skip variable assignment before you call super. However, you can still access them like normal variables:
var image: UIImageView = self.myImageView // no error
Yet another option using a static method (added 'otherView' to highlight scalability)
class MyView: UIView {
var myImageView: UIImageView
var otherView: UIView
override init(frame: CGRect) {
(myImageView,otherView) = MyView.commonInit()
super.init(frame: frame)
}
required init(coder aDecoder: NSCoder) {
(myImageView, otherView) = MyView.commonInit()
super.init(coder: aDecoder)!
}
private static func commonInit() -> (UIImageView, UIView) {
//do whatever initialization stuff is required here
let someImageView = UIImageView(frame: CGRectZero)
someImageView.contentMode = UIViewContentMode.ScaleAspectFill
let someView = UIView(frame: CGRect(x: 0, y: 0, width: 30, height: 30))
return (someImageView, someView)
}
}
Assign myImageView in both the init() methods based on a single image create function. As such:
self.myImageView = self.createMyImageView ();
For example, like such:
class Bar : Foo {
var x : Int?
func createX () -> Int { return 1 }
init () {
super.init ()
self.x = self.createX ()
}
}
Note the 'optional' use at Int?
Additionally, if the intention is to assign myImageView exactly once, it should be a let rather than a var. That rules out some solutions that only work for var.
Another complication is multiple instance variables with dependencies between them. This rules out inline initializers calling static methods.
These requirements can be addressed by overriding with convenience initializers, which delegate to a single designated initializer:
import UIKit
class MyView: UIView {
let myImageView: UIImageView
// Just to illustrate dependencies...
let myContainerView: UIView
override convenience init(frame: CGRect) {
self.init(frame: frame, coder: nil)!
}
required convenience init?(coder aDecoder: NSCoder) {
// Dummy value for `frame`
self.init(frame: CGRect(), coder: aDecoder)
}
#objc private init?(frame: CGRect, coder aDecoder: NSCoder?) {
// All `let`s must be assigned before
// calling `super.init`...
myImageView = UIImageView(frame: CGRect.zero)
myImageView.contentMode = .scaleAspectFill
// Just to illustrate dependencies...
myContainerView = UIView()
myContainerView.addSubview(myImageView)
if let aDecoderNonNil = aDecoder {
super.init(coder: aDecoderNonNil)
} else {
super.init(frame: frame)
}
// After calling `super.init`, can safely reference
// `self` for more common setup...
self.someMethod()
}
...
}
This is based on ylin0x81's answer, which I really like but doesn't work now (build with Xcode 10.2), as load from nib crashes with:
This coder requires that replaced objects be returned from initWithCoder:
This issue is covered on a separate question, with iuriimoz's answer suggesting to add #objc to the designated initializer. That entailed avoiding the Swift-only enum used by ylin0x81.

Resources