Create an adjustable thermometer control using CAShapeLayers
This is a companion discussion topic for the original entry at https://www.raywenderlich.com/4659-drawing-in-ios/lessons/5
Create an adjustable thermometer control using CAShapeLayers
Hi Caroline, once again I have a few questions which I hope you’ll find the time to answer:
draw(:)
method because we are dealing with a CAShapeLayer
, which basically knows how to draw itself?layoutSubviews()
as demonstrated before with the button and the tableview cell, especially because we are referring to the view’s width and height properties? And could you elaborate more on the use of that function in the context of layers? The documentation is not very helpful in that case.lineWidth / 2
theoretically result in 1.5 points → 4.5 pixels on a high resolution screen and thus make the line blurry?Thanks in advance!
draw(_:)
goes into the view’s layer.class View: UIView {
override func draw(_ rect: CGRect) {
UIColor.green.setFill()
let path = UIBezierPath(rect: rect)
path.fill()
}
}
let view = View(frame: CGRect(x: 0, y: 0, width: 500, height: 500))
This results in green at the end of the playground from part 4:
If you take out the draw(_:)
, view.layer.contents will contain nil.
You generally use draw(_:)
when you’re not using CALayers
bounds / 3
, so it’s already compromised. You can try different numbers, take a screenshot of the simulator and zoom in to see what you get, and then run it on your device to see whether the quality is acceptable.layoutSubviews, as you know, is called regularly. This is a good summary:
thermoLayer
’s bounds are always zero. You can print
them to see. The path is being drawn outside of the layer’s bounds. You wouldn’t be able to perform a hit test on this layer.It’s very kind of you that you took the time to respond to all my questions.
Regarding the above quote, doesn’t draw(_:)
draw into the view’s main layer and thus omitting it would, as you explained, leave the main layer’s content to be nil? Maybe I got the concept wrong but I thought that draw(_:)
or the delegate equivalent draw(_:in:)
does work on CALayers
while, in contrast, CAShapeLayer
s, CAGradientLayer
s etc. offer the “self-drawing” functionality, which is why we also don’t need a current context to commit our drawing to. I would be really grateful for some further clarification, if you found the time.
I still don’t quite get how the layer or the drawing sizes itself in the face of those missing bounds. Doesn’t the drawing need the layer’s bounds to be displayed at all (which, as I understood it, relates to the purpose of a CAScrollLayer
and its clipsToBounds
property)? Before starting with this course, I read the corresponding chapters in Matt Neuburg’s “Programming iOS 13” and in this context he explicitly warns:
“A layer created in code (as opposed to a view’s underlying layer) has a frame and bounds of CGRect.zero and will not be visible on the screen even when you add it to a superlayer that is on the screen. Be sure to give your layer a nonzero width and height if you want to be able to see it! Creating a layer and adding it to a superlayer and then wondering why it isn’t appearing is a common beginner error”
Regarding the above quote, doesn’t
draw(_:)
draw into the view’s main layer and thus omitting it would, as you explained, leave the main layer’s content to be nil?
You can always do view.layer.contents = image.cgImage
(or whatever you want to put into that layer. There’s nothing special about the view’s layer. Only that the view always has a layer.
When you assign to a layer’s contents
, you’re effectively saying “this is what I want the layer to show”. But if you want to build up various effects into one layer, that is where you would use draw(_:)
, which will draw into the view’s layer. For example, if you’re drawing a hot dog, which has a sausage, a bun and a squiggle of mustard. You can make several shape layers with paths, or you can draw all three paths into one CGContext. You can use a UIGraphicsImageRenderer context and save the image from there, or you can use draw(_ :)
's context to draw your hot dog straight into your view. If you save the image, that’s a bitmap, and if you resize it, it will become blurry.
But if you were to draw the paths to separate shape layers, then resizing the shape layers won’t become blurry.
If you save the hot dog to an image, then you can assign that image to a layer’s contents
. But you can’t build up a bitmap into a layer’s contents
in the same way as you can in a CGContext.
(I hope I’m not being too obtuse about understanding where your difficulties lie!)
thermoLayer
's bounds are always zero. You can
Matt Neuberg is wonderful, and always right! However, CAShapeLayers are different from other layers, and their bounds can be zero. The paths that form their shape will draw outside the bounds.
Wonderful, thank you! That cleared things up a lot (and now I’m hungry thanks to your hot dog example).
I’ve got one last question, if that’s okay,: I get that prerendering our hot dog using a UIGraphicsImageRenderer
would give us a fixed-size image that gets blurred upon resizing. But wouldn’t, given that we’re drawing the hot dog image into draw(_:)
, upon resizing the view draw(_:)
be called again and cause the image to be rendered again with the new size?
Yes, you’re correct on both counts. UIGraphicsImageRenderer
has a fixed size, and if you resize the view or call setNeedsDisplay()
, then draw(_:)
will redraw the image with the new size.
Great. Once again, thank you very much for your help!