Lazy property with UIView subclass - ios

I want to add UIView subclass property with lazy initialization, for example:
import UIKit
class MyView: UIView {}
class Controller: UIViewController {
lazy var myView = MyView()
}
But I have an error:
Cannot convert values type 'UIView' to specified type 'MyView'
I can fix the error with type of property:
lazy var myView: MyView = MyView()
or change initialization to:
let myView = MyView()
but why Swift cannot inference the type?

The important thing is providing a type if you are an initialized to a variable marked lazy.
lazy var myView:MyView = MyView()

I tried to replicate the issue but with custom class. And did not found any issues.
One thing to be noted is, when the lazy property had no customisations (the defaultValue in sample) compiler did not asked me to provide the explicit type.
But
For the property with customisation (redView), I had to provide the explicit type.
If I did not provide explicit type here is what I got.
Unable to infer complex closure return type; add explicit type to
disambiguate
And this says clearly enough, that the closure's return type cannot be inferred. Its seems obvious because the closure we are using has no explicit return type.
So I tried to supply a closure with explicit type and I was expecting now that now I would not need to provide the explicit type for the redView lazy property. And as expected, it worked without supplying the type for the lazy property.

if you provide init for MyView, then it will be ok.but why? I spent hours to figure out, the result is 😭, waiting for the master to answer.
class MyView: UIView {
init() {
super.init(frame: .zero)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

Related

Swift lazy initialization can't conform to the protocol

I declared several UI components in a protocol.
protocol SomeViewContainer {
var aView: ACustomizedView
...
}
class TestViewController: SomeViewContainer {
var aView: ACustomizedView!
}
The above code won't pass because the compiler doesn't think TestViewController conforms to the protocol.
The aView will be initialized after the data fetched from the remote, so I can't just remove the ! .
In addition, I would prefer to lazy initialzation like the following for some other properties declared in the protocol.
lazy var aView: UIView! = {
}()
Still Failed to compile.
Are there any ideas on how to conform a protocol with lazy initialization?
So two issues, one a property with type ACustomizedView! is not the same as a property with ACustomizedView which is why it doesn't conform
Secondly, you should be able to use lazy.
Is that your actual code?
lazy initialization uses a self executing closure, so it'll run the closure code automatically when called and supply the property with what the closure RETURNS
the fact you have nothing inside the closure will cause it to break..
you need to actually return a view from inside the closure
lazy var aView: UIView = {
let view = UIView()
// configure view
return view
}()

Initializing UIView init AFTER its superclass init?

Looking at the a lecture slide in the Stanford iOS 9 course here, he is creating a new UIView with two initializers (one if the UIView was created from storyboard, and one if it was created in code). The following code is written at the bottom of that particular slide:
func setup() {....} //This contains the initialization code for the newly created UIView
override init(frame: CGRect) { //Initializer if the UIView was created using code.
super.init(frame: frame)
setup()
}
required init(coder aDecoder: NSCoder) { //Initializer if UIView was created in storyboard
super.init(coder:aDecoder)
setup()
}
The rule is that you must initialize ALL of your own properties FIRST before you can grab an init from a superclass. So why is it that in this case he calls his superclass init super.init BEFORE he initializes himself setup()? Doesn't that contradict the following rule:
Safety check 1 A designated initializer must ensure that all of the properties introduced by its class are initialized before it delegates up to a superclass initializer.
As mentioned above, the memory for an object is only considered fully initialized once the initial state of all of its stored properties is known. In order for this rule to be satisfied, a designated initializer must make sure that all its own properties are initialized before it hands off up the chain.
I haven't seen all the rest of the code in this example, but the rule is only that your properties have to be initialized (i.e. the memory they occupy has to be set to some initial value) before calling super.init(), not that you can't run extra setup code.
You can even get away with sort of not-really-initializing your properties by either declaring your properties lazy var, or using var optionals which automatically initialize to nil. You can then set them after your call to super.init().
For example:
class Foo: UIView {
var someSubview: UIView! // initializes automatically to nil
lazy var initialBackgroundColor: UIColor? = {
return self.someSubview.backgroundColor
}()
init() {
super.init(frame: .zero)
setup() // do some other stuff
}
func setup() {
someSubview = UIView()
someSubview.backgroundColor = UIColor.whiteColor()
addSubview(someSubview)
}
}

UIStackView subclasses written in Swift may crash due to unimplemented init(frame:)

I have written a UIStackView subclass, but I am experiencing a strange run-time problem. Here is some sample code where it can be seen:
class SubclassedStackView: UIStackView {
init(text: String, subtext: String) {
let textlabel = UILabel()
let subtextLabel = UILabel()
textlabel.text = text
subtextLabel.text = subtext
super.init(arrangedSubviews: [textlabel, subtextLabel])
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
If you then use it such as this:
let stackView = SubclassedStackView(text: "Test", subtext: "Uh-oh!")
You get a runtime exception with the following message:
fatal error: use of unimplemented initializer 'init(frame:)' for class 'test.SubclassedStackView'
A look at the call stack shows that the base initializer -[UIStackView initWithArrangedSubviews:] is attempting to call init(frame: CGRect) on the subclass, which is intentionally left unimplemented.
Of course I could implement this extra initializer, weird as it would be for it to be called by the superclass, but in my real-life case this would force me to change my properties to use optional types (or implicitly unwrapped optionals) where I shouldn't have to do that.
I could also call init(frame:) instead of init(arrangedSubviews:) and subsequently call addArrangedSubview(view:) to add the arranged subviews. The run-time issue would disappear, but I don't wish to provide a frame.
Why does the superclass's initializer call the subclass's initializer? Can anyone suggest a way to work around this issue without introducing optionals?
Edit: Apple acknowledged this bug which should be fixed in iOS 10. http://www.openradar.me/radar?id=4989179939258368 Still applies to iOS 8-9 unfortunately.
I'm not sure if this will work for your needs, but I've managed to circumvent the problem with an extension on UIStackView:
extension UIStackView {
convenience init(text: String, subtext: String) {
let textlabel = UILabel()
let subtextLabel = UILabel()
textlabel.text = text
subtextLabel.text = subtext
self.init(arrangedSubviews: [textlabel, subtextLabel])
}
}
// ...
let sv = UIStackView(text: "", subtext: "") // <UIStackView: 0x7fcd32022c20; frame = (0 0; 0 0); layer = <CATransformLayer: 0x7fcd32030810>>
A look at the call stack shows that the base initialiser -[UIStackView initWithArrangedSubviews:] is attempting to call init(frame: CGRect) on the subclass, which is intentionally left unimplemented.
Why not just add the missing constructor?
override init(frame: CGRect) {
super.init(frame: frame)
}
The problem's coming from the frame initializer not being available since you've provided your own, and it needs that for its internal implementation. If the frame will be managed by AutoLayout anyways you don't need to be concerned with what it's actually set to initially, so you can just let it perform its internal routines necessary to initialize with your subviews. I don't see from the code above why you would need to add optionals..
init(arrangedSubviews: [] is a convenience initializer. As per documentation, you must call the superclass's designated initializer (which is init(frame:)) instead

Why must I keep declaring the same required but not implemented initializer for init(coder aDecoder) for my programatic UIViewController subclass?

Perhaps it is just me, but I find certain aspects of swift... obtuse to say the least.
I don't use Interface Builder most of the time because I like using PureLayout. So I was hoping to make a UIViewController subclass, say PureViewController, that had a handy init without parameters:
class PureViewController : UIViewController {
init() {
super.init(nibName: nil, bundle: nil)
}
}
But this is not okay, for XCode tells me I must also implement init(coder aDecoder: NSCoder). Okay, that's fine! That's why I made this class - so I don't have to do this again for subclasses.
class PureViewController : UIViewController {
init() {
super.init(nibName: nil, bundle: nil)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Ok, now here's what I don't get.
I define a subclass, SomePureViewController : PureViewController, with an initializer init(viewModel:ICrackersViewModel)...
class SomePureViewController : PureViewController {
init(viewModel:ICrackersViewModel) {
super.init()
}
}
But it STILL wants me to define the same stupid initializer till kingdom come!
class SomePureViewController : PureViewController {
init(viewModel:ICrackersViewModel) {
super.init()
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Now I understand the idea - there is no init(decoder) in my subclass, even though it is defined in its parent class.
Maybe I've always dealt with this issue with UIViewController and never noticed it before.
My questions are as follows:
Is there something I am doing wrong to cause this behavior?
Is there any way outside of inheritance that I can avoid repeating myself?
Are there plans to any plans to change this?
The point is that one can initialize a possibly derived class just by knowing the base type.
Lets assume a base class
class Base {
let value: Int
required init(value: Int) {
self.value = value
}
}
and a function
func instantiateWith5(cls: Base.Type) -> Base {
return cls.init(value: 5)
}
then we can do
let object = instantiateWith5(Base.self)
Now if someone defines a derived class
class Derived: Base {
let otherValue: Int
init() {
otherValue = 1
super.init(value: 1)
}
required init(value: Int) {
fatalError("init(value:) has not been implemented")
}
}
We are at least able to call
let object2 = instantiateWith5(Derived.self)
violating LSP, but that's a problem of your code, not the language.
Swift has to guarantee that initializers leave your objects in a initialized state, even when they are derived from a base object, so I guess changing that would be a bad thing. If you like to define a UIViewController that is not deserializable and thereby violating LSP it's your decision - but don't expect the language to support you in this.
I think that this is swift issue and there is no way how to avoid this. We all hate this empty - fatal error initializer.
As the initialiser is marked with the required keyword, all subclasses must implement that initialiser and they must also specify required on their implementation so that their subclasses are also required to implement it.
Required Initializers
Write the required modifier before the
definition of a class initializer to indicate that every subclass of
the class must implement that initializer
You must also write the required modifier before every subclass implementation of a required initializer, to indicate that the initializer requirement applies to further subclasses in the chain.”
This has been part of the Swift language since 1.0 and it is unlikely to change.
The issue is actually to do with the use of the required keyword in the UIViewController class definition. In theory this could be changed, but again I think it is unlikely.

swift: handling unwrapped optional in viewDidLoad

I have actually some troubles handling unwrapped optional constant on UIViewController.
Because UI components exist only after viewDidLoad calls, It seems I can't use the 'let' constant modifier on my constant variables who need GUI dependancy.
Here is an example:
class ViewController: UIViewController {
#IBOutlet weak var blueSquare: UIView!
var animator:UIDynamicAnimator!
required init(coder aDecoder: NSCoder) {
// If animator was constant, it should be initialized here.
// But blueSquare is not initialized at this time, so I can't
// call UIDynamicAnimator(referenceView: blueSquare)
super.init(coder: aDecoder)
}
override func viewDidLoad() {
super.viewDidLoad()
// At this time, the initialisation is ok. But animator is now
// actually modifiable
self.animator = UIDynamicAnimator(referenceView: blueSquare)
}
}
As you can see, animator is written as if it can be modifiable. The intend isn't.
My question is: is there any nice pattern who can put 'animator' as constant ? I didn't find any documentation dealing with viewDidLoad and constants with GUI dependency paradigm.
That is why you should leverage optionals.
When you declare:
var animator:UIDynamicAnimator!
You make the compiler believe that animator doesn't have to be initialized before calling init(coder). That's a dangerous game you are playing here. Instead you'd much rather use:
var animator:UIDynamicAnimator?
so using the var before it is initialized is reported as an error. Then you can set it up in viewDidLoad and things are back to where they should be.
You are right about assuming that blueSquare can be used only later in the controller's lifecycle and this has nothing to do with Swift syntax; it is a framework constraint. Therefore you need to have animator declared as a dangling reference till it can be inited to something useful. That's what optional provides to you.

Resources