Reputation: 477
Why the below code throws fatal error: unexpectedly found nil while unwrapping an Optional value
when run in Xcode playground? I am not sure what is wrong with the below code. Thanks for your help. I haven't tried running it outside playground.
import UIKit
import XCPlayground
class CircularProgressView: UIView {
var progressBackgroundLayer: CAShapeLayer!
var progressLayer: CAShapeLayer!
var iconLayer: CAShapeLayer!
override init(frame: CGRect) {
super.init(frame: frame)
self.setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.setup()
}
convenience init() {
self.init(frame: CGRectZero)
}
func setup() {
progressBackgroundLayer = CAShapeLayer(layer: layer)
progressLayer = CAShapeLayer(layer: layer)
iconLayer = CAShapeLayer(layer: layer)
}
override func drawRect(rect: CGRect) {
progressBackgroundLayer.frame = self.bounds
progressLayer.frame = self.bounds
iconLayer.frame = self.bounds
}
}
var progressView = CircularProgressView(frame: CGRectMake(0, 0, 80, 80))
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
XCPlaygroundPage.currentPage.liveView = progressView
Upvotes: 0
Views: 318
Reputation: 80931
You shouldn't use the init(layer:)
initialiser in order to create your CAShapeLayers
. From the documentation (emphasis mine):
This initializer is used to create shadow copies of layers, for example, for the presentationLayer method. Using this method in any other situation will produce undefined behavior. For example, do not use this method to initialize a new layer with an existing layer’s content.
Therefore, as the behaviour of calling this initialiser is undefined in this circumstance, it's returning nil
in your case – which you're then force unwrapping upon accessing, as it's an implicitly unwrapped optional.
You should instead just create your layers with init()
. I would also recommend that you define your layer instances inline, and get rid of the implicitly unwrapped optionals, as they're inherently unsafe. For example:
class CircularProgressView: UIView {
let progressBackgroundLayer = CAShapeLayer()
let progressLayer = CAShapeLayer()
let iconLayer = CAShapeLayer()
...
In order to add the layers to your view, you need to use the addSublayer:
method on the view's layer. For example:
func setup() {
layer.addSublayer(progressBackgroundLayer)
layer.addSublayer(progressLayer)
layer.addSublayer(iconLayer)
}
Also, drawRect:
is used to draw the contents of your view, and therefore is totally the wrong place to be defining the frames of your layers (which should only occur when the view's bounds changes). You should instead consider doing this in layoutSubviews
.
override func layoutSubviews() {
progressBackgroundLayer.frame = bounds
progressLayer.frame = bounds
iconLayer.frame = bounds
}
And finally, this code is pointless:
convenience init() {
self.init(frame: CGRectZero)
}
This is exactly what the UIView
implementation of init
does already.
Upvotes: 2