com.iavian
com.iavian

Reputation: 477

drawRect throws "fatal error: unexpectedly found nil while unwrapping an Optional value" in Xcode playground

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

Answers (1)

Hamish
Hamish

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

Related Questions