yerpy
yerpy

Reputation: 1446

Seems like saving graphics context doesn't work

I've written a model for my custom view. The view has caption with dropped shadow and in my example 4 strips bellow.

My problem is whenever I've created a model for view the shadow doesn't appear when I save the context - I want display shadow only bellow caption, not in each shape inside view.

Model :

import UIKit

protocol StripeDrawable {
    func drawCaption<T: CGContext>(in rect: CGRect, using ctx: T)
    func drawSripes<T: CGContext>(in rect: CGRect, using ctx: T)
}

struct Stripe {
    var numberOfStripes: Int
    var stripPattern: [UIColor]
}

struct Caption {
    var captionFontSize: CGFloat
    var captionHeight: CGFloat
    var captionTitle: String
    var captionBackgroundColor: UIColor
    var captionTextColor: UIColor
    var isCaptionShadowEnabled: Bool
}

class StripeAxis {

    var stripe: Stripe
    var caption: Caption

    init(stripe: Stripe, caption: Caption) {
        self.stripe = stripe
        self.caption = caption
    }

}

extension StripeAxis: StripeDrawable {
    func drawCaption<T>(in rect: CGRect, using ctx: T) where T : CGContext {
        let captionRect = CGRect(x: 0, y: 0, width: rect.width, height: caption.captionHeight)

        ctx.addRect(captionRect)

        ctx.saveGState()

        if caption.isCaptionShadowEnabled {
            ctx.setShadow(offset: CGSize(width: 0, height: 0), blur: 3, color: UIColor.black.cgColor)
        }

        ctx.setFillColor(caption.captionBackgroundColor.cgColor)
        ctx.fill(captionRect)

        let font = UIFont.boldSystemFont(ofSize: caption.captionFontSize)

        let attribs = [NSAttributedStringKey.foregroundColor : caption.captionTextColor,
                       NSAttributedStringKey.font : font] as [NSAttributedStringKey : Any]

        caption.captionTitle.center(in: captionRect, attribs: attribs as! [NSAttributedStringKey : NSObject])

        ctx.restoreGState()
    }

    func drawSripes<T>(in rect: CGRect, using ctx: T) where T : CGContext {
        let stripHeight: CGFloat = (rect.height - caption.captionHeight) / CGFloat(stripe.numberOfStripes)

        for i in 0...stripe.numberOfStripes - 1 {
            var color: UIColor!
            let stripRect = CGRect(x: 0, y: stripHeight * CGFloat(i) + caption.captionHeight, width: rect.width, height: stripHeight)

            if i % 2 == 0 {
                color = stripe.stripPattern[0]
            } else {
                color = stripe.stripPattern[1]
            }

            ctx.setFillColor(color.cgColor)
            ctx.fill(stripRect)
        }
    }
}

Custom view :

import UIKit

final class TimersStripesView: UIView {

    var selectedIndex: Int = 0

    var statusRects: [CGRect] = []

    var onSelection: (() -> ())!

    private let pad = UIDevice.current.userInterfaceIdiom == .pad

    var stripeAxis: StripeAxis!

    override init(frame: CGRect) {
        super.init(frame: frame)

        stripeAxis = StripeAxis(stripe: Stripe(numberOfStripes: 4, stripPattern: [.red, .blue]),
                                caption: Caption(captionFontSize: pad ? 15 : 11, captionHeight: pad ? 40 : 25, captionTitle: "TOTAL", captionBackgroundColor: .orange, captionTextColor: .white, isCaptionShadowEnabled: true))

        contentMode = .redraw

        //        addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(onTouch(_:))))
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }



    override func draw(_ rect: CGRect) {
        super.draw(rect)

        guard let ctx = UIGraphicsGetCurrentContext() else { return }

        stripeAxis.drawCaption(in: rect, using: ctx)
        stripeAxis.drawSripes(in: rect, using: ctx)
    }
}

As we can see shadow has dropped on caption titile, but it doesn't drop bellow rect.

Before changes when I had no model, simply when I did the same:

In draw:

override func draw(_ rect: CGRect) {
    super.draw(rect)
    guard let ctx = UIGraphicsGetCurrentContext() else { return }
    let captionRect = CGRect(x: 0, y: 0, width: rect.width, height: pad ? 40.0 : 25.0)
    drawCaption(in: captionRect, using: ctx)
    drawStrips(using: ctx, in: rect, captionHeight: pad ? 40.0 : 25.0)
}

And the method:

func drawCaption<T: CGContext>(in rect: CGRect, using ctx: T) {
    ctx.addRect(rect)

    ctx.saveGState()

    ctx.setShadow(offset: CGSize(width: 0, height: 0), blur: 3, color: UIColor.black.cgColor)

    ctx.setFillColor(UIColor.orange.cgColor)
    ctx.fill(rect)

    let statusText = "TOTAL"

    let font = UIFont.boldSystemFont(ofSize: pad ? 15 : 11)
    let attribs = [NSAttributedStringKey.foregroundColor : UIColor.white, NSAttributedStringKey.font: font]

    statusText.center(in: rect, attribs: attribs)

    ctx.restoreGState()
}

func drawStrips<T: CGContext>(using ctx: T, in rect: CGRect, captionHeight: CGFloat) {
    let statusHeight: CGFloat = (rect.height - captionHeight) / 4

    for i in 0...3 {
        var color: UIColor!
        let newRect = CGRect(x: 0, y: statusHeight * CGFloat(i) + captionHeight, width: rect.width, height: statusHeight)

        if i % 2 == 0 {
            color = .red
        } else {
            color = .blue
        }
        ctx.setFillColor(color.cgColor)
        ctx.fill(newRect)
    }
}

Was working with result :

enter image description here

I can't see where I've done mistake, any ideas?

Upvotes: 1

Views: 185

Answers (1)

jrturton
jrturton

Reputation: 119242

Your previous code wasn't working (I ran it in a playground) - the shadow was also missing.

There's no problem with saving and restoring the graphics state. What's happening is simply that when you draw the stripes, they are drawn on top of the shadow from the orange box, so you can't see it any more. Simply change the order of the drawing calls so that the stripes are drawn before the caption, and it all works fine.

Upvotes: 1

Related Questions