3
votes

Say you have this in a UIView,

override func draw(_ rect: CGRect) {
    let c = UIGraphicsGetCurrentContext()
    c?.setLineWidth(10.0)
    c?.move(to: CGPoint(x: 10.0, y: 10.0))
    c?.addLine(to: CGPoint(x: 40.0, y: 40.0))
    ... lots of complicated drawing here, sundry paths, colors, widths etc ...
    c?.strokePath()
}

Of course, it will draw the hell out of your drawing for you.

But say in the same UIView you do this ...

func setup() { // in inits, layout
    if nil etc {
      thingLayer = CAShapeLayer()
      self.layer.insertSublayer(thingLayer, at: 0)
      thingLayer.fillColor = UIColor.yellow.cgColor }
    thingLayer.frame = bounds
    let path = ... some fancy path
    thingLayer.path = path.cgPath
}

Indeed, the new yellow layer is drawn over the drawing in draw#rect.

How do you draw - using core graphics commands - either on to thingLayer, or perhaps on to another layer on top of all??

Core graphics commands:

    let c = UIGraphicsGetCurrentContext()
    c?.setLineWidth(10.0)
    c?.move(to: CGPoint(x: 10.0, y: 10.0))
    c?.addLine(to: CGPoint(x: 40.0, y: 40.0))
    c?.strokePath()

seem to draw to a place directly above or on the main .layer

(Well that appears to be the case, as far as I can see.)

How do you draw - using core graphics commands - either on to thingLayer, or perhaps on to another layer on top of all??

Surely in draw#rect you can specify which cgLayer to draw to?

In draw#rect, can you make another cgLayer and draw to context, that cgLayer?

3
By the way, if you do anything based upon the bounds, you’ll want to move that to layoutSubviews.Rob
I'm not sure if you noticed, I add bounties to every QA. "One or more of the answers is exemplary and worthy of an additional bounty." Also it often brings more interest and other worthwhile ideas, answers or comments, I've found. thanks again!Fattie

3 Answers

2
votes

Bottom line, the draw(_:) is for rendering the root view of a UIView subclass. If you add sublayers, they will be rendered on top of whatever is done in the root view’s draw(_:).

If you have a few paths currently performed in the draw(_:) that you want to render on top of the sublayer that you’ve added, you have a few options:

  1. Move the paths to be stroked in their own CAShapeLayer instances and add them above the other sublayer that you’ve already added.

  2. Consider the main UIView subclass as a container/content view, and add your own private UIView subviews (potentially with the “complicated” draw(_:) methods). You don’t have to expose these private subviews if you don’t want to.

  3. Move the complicated drawing to the draw(in:) of a CALayer subclass and, again, add this sublayer above the existing sublayer that you’ve already created.

  4. Given that the thingLayer is, effectively, only setting the background color, you could just set the background color of the view and not use a layer for this background color at all.

1
votes

The CALayer hierarchy is a tree, where a root layer is first (e.g. the layer that comprises the background of a window), followed by its sublayers (an array of CALayer objects, stored and drawn in in back-to-front order), followed by their sublayers, and so on, recursively.

Your implementation of UIView.draw(_ rect: CGRect) is defining how the main layer of your view (self.layer) is drawn.

By adding your CAShapeLayer as a sublayer of self.layer, you're making it be draw after self.layer, which has the effect of it being drawn above self.layer, which contains the line you drew in UIView.draw(_ rect: CGRect).

To resolve this, you need to put your stroke in sublayer after your thingLayer.

self.layer.insertSublayer(thingLayer, at: 0)
self.layer.insertSublayer(lineLayer, above: thingLayer)

By the way, you forgot to delegate to super.draw(rect). It's not necessary for direct subclasses of UIView (since the base implementation doesn't do anything), but it is necessary for every other case, and it's generally a good habit to get into (lest you run into really obscure/frustrating drawing bugs).

0
votes

If your yellow shape layer doesn't change or move around independently, you could get rid of the CAShapeLayer and just draw the shape yourself at the top of your implementation of draw(_:):

let path = // your fancy UIBezierPath
UIColor.yellow.setFill()
path.fill()

Otherwise, as other commenters have said, you'll need to move your drawing code into additional custom subclasses of UIView or CALayer, and stack them in the order you want.