How to create custom init with frame for UIView [duplicate] - ios

This question already has answers here:
Error in Swift class: Property not initialized at super.init call
(12 answers)
Closed 8 years ago.
I am getting Property self.color not initialized as super.init call error with the below code. How can I properly override the init(frame:) function please? I'd like to pass the color along with the init call.
class CircleView: UIView {
// properties
let color: UIColor
init(frame: CGRect, color: UIColor) {
self.color = color
super.init(frame: frame)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func drawRect(rect: CGRect) {
...
}
}

Note that any property declared as "let" must be initialized during all possible inits. You are not doing it when the deserialization init is invoked. Thats why it does not compile.
Now that you understand the reason, let's go for the solution. You can make your constant an optional variable, so it does not need to be initialized, which should solve the problem but add another problem: now it is mutable.
If you still want to keep a let (and you probably want, otherwise you would have already defined a var), you need to decode the content from the coder that the second init receives. While doing that you must also override the serialization process and write the color value so your uiview can be properly serialized.
If you are not caring at all about serialization, the first option solves your problem. If you care about what's happening to your code, I suggest going for understanding the serialization API in Swift and implementing the proper init(decoder) and encoder method.

Related

When is "required init?(coder aDecoder: NSCoder)" called on a UIView or UIViewController?

When I create a subclass of UIView or UIViewController with a stored property, Xcode will not compile my project unless I include an implementation of required init?(coder aDecoder: NSCoder). Currently, I have the following implementation to shut the compiler up:
required init?(coder aDecoder: NSCoder) {
fatalError()
}
I understand why I'm required to include this initializer; my subclass needs to conform to the NSCoding protocol because its superclass conforms to it, and this initializer is part of the NSCoding protocol so it needs to work with my class, i.e. initialize all of my class's stored properties (which the superclass version of the initializer won't do).
I imagine that a correct implementation would look something like this:
class MyView: UIView {
let label: UILabel
override init(frame: CGRect) {
label = UILabel()
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
if let label = aDecoder.decodeObject() as? UILabel {
self.label = label
} else {
return nil
}
super.init(coder: aDecoder)
}
override func encode(with aCoder: NSCoder) {
aCoder.encode(label)
super.encode(with: aCoder)
}
}
However, considering that my application has over 50 custom views and view controllers, correctly implementing this function in every custom view and view controller is a lot of work.
So, I'm wondering if it's necessary to implement this initializer correctly, or if I can just leave it throwing a fatal error. In other words, will this initializer ever be called if I don't call it in my own code? I think I read that it might be called by a Storyboard, but my app doesn't use any Storyboards.
This initialiser will be called if an instance of your view is used in a storyboard scene.
It is up to you whether to create a functioning initialiser or not, but it should mostly be a matter of copying code from init(frame:)
It provides an NSCoder instance as a parameter, which you need only if you are using iOS serialization APIs. This is not used often, so you can ignore it. If you are curious to learn, serialisation converts an object in a byte stream that you can save on disk or send over the network.
During the initalization of a view controller, you usually allocate the resources that the view controller will need during its lifetime. So, this include model objects or other auxiliary controllers, like network controllers.

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

Swift, super.init() Must call a designated initializer of the superclass 'UIView' error [duplicate]

This question already has answers here:
Build error when trying to override an initializer in Xcode 6.3 Beta 3
(3 answers)
Closed 7 years ago.
I am new with Swift. I inherited a project. I saw it running on a device. However, when I checkout the code and it had many errors. I was able to clear out the errors. However, I run into this one that is baffling me. The project uses xib files as well. Here is the code.
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(frame: CGRect) {
super.init(frame: frame)
}
init(items:NSArray, viewController:AnyObject){
super.init()
//itemsArray = items
itemsArray = items as [AnyObject]
//commonInit(viewController as UIViewController)
commonInit(viewController as! UIViewController)
}
I get the error under the init(items:NSArray, viewController:AnyObject) method/function. The error is pointed at the "super.init()". It states "Must call a designated initializer of the superclass 'UIView' error"
I been searching, googling, asking others and nothing has turned up.
Can I get help on fixing this error, or at least why this error happens? I would like understand so I can become a better software developer.
Edit: I would like to thank everyone for their insight and help. I found out the problem is bigger. I did the changes suggested in the super.init(frame: CGRect). I had to change an array property as well which was affecting the init function.
As the error message suggests you can only call the designated initializer of the superclass.
To solve this you need to call : super.init(frame: frame) instead of super.init()
UIView's designated initializers are:
init(frame: CGRect)
init(coder aDecoder: NSCoder)
You're calling super.init() which is an initializer inherited from its superclass NSObject. Use one of those initialises instead as the compiler is suggesting in order to (actually) invoke the super constructor which could be one of the 2 above.
If it helps, another example is if you inherits from UIViewController and then you create your own init you can only invoke one the two:
init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?)
init?(coder: NSCoder)
i.e:
init() {
super.init(nibName: nil, bundle: nil)
}

Adding additional init code to an inherited initializer from superclass

I have a Swift UIView class (named HypnosisView) that draws a circle on the screen. The frame of the view is set to fill the screen. I would like to programmatically set the background color of the view upon initialization (so when an instance of the view is created it automatically has the specified background color). I was able to make this work with a convenience initializer, however I'm wondering if there is a more efficient way to do this (or if in fact I'm doing this correctly). In an ideal scenario, I would like to just add a piece of code that sets the background: self.background = UIColor.clearColor() to the inherited init(frame: CGRect) method, so I don't have to write a whole new initializer just to set the background color. Here is my convenience initializer method (what I'm currently using which works):
convenience init(rect: CGRect){
self.init(frame: rect)
self.backgroundColor = UIColor.clearColor()
}
and I call that method in the delegate like this:
var mainFrame = self.window!.bounds
var mainView = HypnosisView(rect: mainFrame)
Let me know if you have any questions. Thanks!
As discussed in the comments, when wanting to customize the behavior of a UIView, it's often easier to use a convenience initializer as opposed to overriding a designated initializer.
For UIViews specifically, if you override the designated init(frame aRect: CGRect), you are unfortunately also required to override init(coder decoder: NSCoder!) which is part of the NSCoding protocol. So generally if you just want to set a few properties to some default values, do as the original poster asked and create a convenience initializer that in turn calls init(frame aRect: CGRect):
convenience init(rect: CGRect, bgColor: UIColor){
self.init(frame: rect)
self.backgroundColor = bgColor
}
For a discussion on getting rid of NSCoding compliance, see Class does not implement its superclass's required members

Resources