Handle UIView common initialization in swift - ios

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

Related

How to properly init passed property in subclass of UIView?

I have a subclass of UIView that I would like to pass a property to. As much as I've tried, I don't truly understand all elements of initializing.
Here is a simplified version of my code:
class inputWithIncrementView : UIView, UITextFieldDelegate {
var inputName : String // This is the property I want to receive and init
override init (frame : CGRect) {
super.init(frame : frame)
// [this is where i will use the inputName property passed on initialization]
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
// [other functions and stuff working fine here]
}
I have tried a number of things, but I'm getting confused between the UIView initializer and the way I normally initialize a non-subclassed class.
How do I modify this code to receive the string property, initialize it? Thanks
If you want to initialize a UIView with a custom property you must reconfigure its initializer:
class InputWithIncrementView: UIView {
let inputName: String
init(inputName: String) {
self.inputName = inputName
super.init(frame: .zero)
}
required init?(coder aDecoder: NSCoder) {
return nil
}
}

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.

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
}

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