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
Related
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)
}
}
I'm using Swift, and I find myself having to subclass UIKit classes such as UIView and UIButton. I don't care about setting the frame since I'm using AutoLayout, so I don't want/need to use init(frame: CGRect).
class customSubclass: UIView {
var logo: UIImage
init(logo: UIImage) {
self.logo = logo
//compiler yells at me since super.init() isn't called before return from initializer
//so I end up doing this
super.init(frame: CGRectZero)
self.translatesAutoresizingMaskIntoConstraints = false
}
I also don't find it very sexy to set it's frame to CGRectZero.
Is there a way to having a custom initializer for a subclass of a UIView or UIButton without explicitly setting it's frame?
Note Every subclass is instantiated in code, so required init(coder: aDecoder) is in my code, but isn't actually doing anything.
Thanks!
During initialization of a subclass, you must call the designated initializer of a superclass. In your case, since you are creating these views programmatically, you must use super.init(frame: CGRect). As you mentioned, it would be useful to implement two designated initializers for your subclass, one of which takes in a frame:CGRect argument.
Please see the accepted answer to this question for a more thorough review.
If you're using autolayout in a storyboard or xib, then just override the init(coder:) method so that it calls the superclass's version, and have your convenience initializers pass CGRectZero or some other value in.
Instead of creating a new class and subclassing, you could try using extensions.
extension UIView {
var logo: UIImage = myImage
func myLogo() {
// code here
}
}
I'm using Swift and Xcode 6.4 for what it's worth.
So I have a view controller that will be containing some multiple pairs of UILabels and UIImageViews. I wanted to put the UILabel-UIImageView pair into a custom UIView, so I could simply reuse the same structure repeatedly within the aforementioned view controller. (I'm aware this could be translated into a UITableView, but for the sake of ~learning~ please bear with me). This is turning out to be a more convoluted process than I imagined it would be, I'm having trouble figuring out the "right" way to make this all work in IB.
Currently I've been floundering around with a UIView subclass and corresponding XIB, overriding init(frame:) and init(coder), loading the view from the nib and adding it as a subview. This is what I've seen/read around the internet so far. (This is approximately it: http://iphonedev.tv/blog/2014/12/15/create-an-ibdesignable-uiview-subclass-with-code-from-an-xib-file-in-xcode-6).
This gave me the problem of causing an infinite loop between init(coder) and loading the nib from the bundle. Strangely none of these articles or previous answers on stack overflow mention this!
Ok so I put a check in init(coder) to see if the subview had already been added. That "solved" that, seemingly. However I started running into an issue with my custom view outlets being nil by the time I try to assign values to them.
I made a didSet and added a breakpoint to take a look...they are definitely being set at one point, but by the time I try to, say, modify the textColor of a label, that label is nil.
I'm kind of tearing my hair out here.
Reusable components seem like software design 101, but I feel like Apple is conspiring against me. Should I be looking to use container VCs here? Should I just be nesting views and having a stupidly huge amount of outlets in my main VC? Why is this so convoluted? Why do everyone's examples NOT work for me?
Desired result (pretend the whole thing is the VC, the boxes are the custom uiviews I want):
Thanks for reading.
Following is my custom UIView subclass. In my main storyboard, I have UIViews with the subclass set as their class.
class StageCardView: UIView {
#IBOutlet weak private var stageLabel: UILabel! {
didSet {
NSLog("I will murder you %#", stageLabel)
}
}
#IBOutlet weak private var stageImage: UIImageView!
var stageName : String? {
didSet {
self.stageLabel.text = stageName
}
}
var imageName : String? {
didSet {
self.stageImage.image = UIImage(named: imageName!)
}
}
var textColor : UIColor? {
didSet {
self.stageLabel.textColor = textColor
}
}
var splatColor : UIColor? {
didSet {
let splatImage = UIImage(named: "backsplat")?.tintedImageWithColor(splatColor!)
self.backgroundColor = UIColor(patternImage: splatImage!)
}
}
// MARK: init
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
if self.subviews.count == 0 {
setup()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
func setup() {
if let view = NSBundle.mainBundle().loadNibNamed("StageCardView", owner: self, options: nil).first as? StageCardView {
view.frame = bounds
view.autoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight
addSubview(view)
}
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func drawRect(rect: CGRect) {
// Drawing code
}
*/
}
EDIT: Here's what I've been able to get so far...
XIB:
Result:
Problem: When trying to access label or image outlets, they are nil. When checking at breakpoint of said access, the label and image subviews are there and the view hierarchy is as expected.
I'm OK with doing this all in code if thats what it takes, but I'm not huge into doing Autolayout in code so I'd rather not if there's a way to avoid it!
EDIT/QUESTION SHIFT:
I figured out how to make the outlets stop being nil.
Inspiration from this SO answer: Loaded nib but the view outlet was not set - new to InterfaceBuilder except instead of assigning the view outlet I assigned the individual component outlets.
Now this was at the point where I was just flinging shit at a wall and seeing if it'd stick. Does anyone know why I had to do this? What sort of dark magic is this?
General advice on view re-use
You're right, re-usable and composable elements is software 101. Interface Builder is not very good at it.
Specifically, xibs and storyboard are great ways to define views by re-using views that are defined in code. But they are not very good for defining views that you yourself wish to re-use within xibs and storyboards. (It can be done, but it is an advanced exercise.)
So, here's a rule of thumb. If you are defining a view that you want to re-use from code, then define it however you wish. But if you are defining a view that you want to be able to re-use possibly from within a storyboard, then define that view in code.
So in your case, if you're trying to define a custom view which you want to re-use from a storyboard, I'd do it in code. If you are dead set on defining your view via a xib, then I'd define a view in code and in its initializer have it initialize your xib-defined view and configure that as a subview.
Advice in this case
Here's roughly how you'd define your view in code:
class StageCardView: UIView {
var stageLabel = UILabel(frame:CGRectZero)
var stageImage = UIImageView(frame:CGRectZero)
override init(frame:CGRect) {
super.init(frame:frame)
setup()
}
required init(coder aDecoder:NSCoder) {
super.init(coder:aDecoder)
setup()
}
private func setup() {
stageImage.image = UIImage(named:"backsplat")
self.addSubview(stageLabel)
self.addSubview(stageImage)
// configure the initial layout of your subviews here.
}
}
You can now instantiate this in code and or via a storyboard, although you won't get a live preview in Interface Builder as is.
And alternatively, here's roughly how you might define a re-usable view based fundamentally on a xib, by embedding the xib-defined view in a code-defined view:
class StageCardView: UIView {
var embeddedView:EmbeddedView!
override init(frame:CGRect) {
super.init(frame:frame)
setup()
}
required init(coder aDecoder:NSCoder) {
super.init(coder:aDecoder)
setup()
}
private func setup() {
self.embeddedView = NSBundle.mainBundle().loadNibNamed("EmbeddedView",owner:self,options:nil).lastObject as! UIView
self.addSubview(self.embeddedView)
self.embeddedView.frame = self.bounds
self.embeddedView.autoresizingMask = .FlexibleHeight | .FlexibleWidth
}
}
Now you can use the code-defined view from storyboards or from code, and it will load its nib-defined subview (and there's still no live preview in IB).
I was able to work it around but the solution is a little bit tricky. It's up to debate if the gain is worth an effort but here is how I implemented it purely in interface builder
First I defined a custom UIView subclass named P2View
#IBDesignable class P2View: UIView
{
#IBOutlet private weak var titleLabel: UILabel!
#IBOutlet private weak var iconView: UIImageView!
#IBInspectable var title: String? {
didSet {
if titleLabel != nil {
titleLabel.text = title
}
}
}
#IBInspectable var image: UIImage? {
didSet {
if iconView != nil {
iconView.image = image
}
}
}
override init(frame: CGRect)
{
super.init(frame: frame)
awakeFromNib()
}
required init?(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
}
override func awakeFromNib()
{
super.awakeFromNib()
let bundle = Bundle(for: type(of: self))
guard let view = bundle.loadNibNamed("P2View", owner: self, options: nil)?.first as? UIView else {
return
}
view.translatesAutoresizingMaskIntoConstraints = false
addSubview(view)
let bindings = ["view": view]
let verticalConstraints = NSLayoutConstraint.constraints(withVisualFormat:"V:|-0-[view]-0-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: bindings)
let horizontalConstraints = NSLayoutConstraint.constraints(withVisualFormat:"H:|-0-[view]-0-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: bindings)
addConstraints(verticalConstraints)
addConstraints(horizontalConstraints)
}
titleLabel.text = title
iconView.image = image
}
This is how it looks like in interface builder
This is how I embedded this custom view in the example view controller defined on a storyboard. Properties of P2View are set in the attributes inspector.
There are 3 points worth mentioning
First:
Use the Bundle(for: type(of: self)) when loading the nib. This is because the interface builder renders the designables in the separate process which main bundle is not the same as your main bundle.
Second:
#IBInspectable var title: String? {
didSet {
if titleLabel != nil {
titleLabel.text = title
}
}
}
When combining IBInspectables with IBOutlets you have to remember that the didSet functions are called before awakeFromNib method. Because of that, the outlets are not initialized and your app will probably crash at this point. Unfortunatelly you cannot omit the didSet function because the interface builder won't render your custom view so we have to leave this dirty if here.
Third:
titleLabel.text = title
iconView.image = image
We have to somehow initialize our controls. We were not able to do it when didSet function was called so we have to use the value stored in the IBInspectable properties and initialize them at the end of the awakeFromNib method.
This is how you can implement a custom view on a Xib, embed it on a storyboard, configure it on a storyboard, have it rendered and have a non-crashing app. It requires a hack, but it's possible.
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.
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