2

I would like to develop a kind of progress bar animation for AppleWatch. I decided to do it all with the UIGraphicContext.

Since I'm a complete beginner I don't really understand how I can apply some kind of "globalCompositeOperation" to my context.

To illustrate my idea better here are some pictures:

Image #1

Image #2

Image #3


This is my source code so far:

let size = CGSize(width: 300, height: 100)
UIGraphicsBeginImageContext(size)

let context = UIGraphicsGetCurrentContext()!

context.setFillColor(UIColor.red.cgColor)
context.fill(CGRect(x: 0.0, y: 0.0, width: size.width * 0.5, height: size.height))
context.setBlendMode(CGBlendMode.destinationOver)

let count = 5;
let padding = (size.width / CGFloat(count)) * 0.5
for i in 0...count {
    let offsetX = size.width * (CGFloat(i) / CGFloat(count))
    let rect = CGRect(x: offsetX + padding/2, y: 0, width: padding, height: size.height)
    context.setFillColor(UIColor.green.cgColor)
    context.fill(rect)
}
UIGraphicsEndImageContext()

I guess this is kind of the wrong approach because the result is more like this:

Image #4

Any help would be really appreciated. Thanks in advance.

Scriptable
  • 19,402
  • 5
  • 56
  • 72
stacj
  • 1,103
  • 1
  • 7
  • 20
  • I guess this is not a valid Watchkit solution: The guy from the post is talking about stacking multiple views - this is no option for WK. Thanks anyways. @matt – stacj Jan 25 '20 at 14:27

2 Answers2

2

You want .sourceAtop. Here's a demonstration:

    let green = UIImage(named:"green.png")! // your original 5 green rects
    let sz = green.size
    let red = UIGraphicsImageRenderer(size:sz).image {
        ctx in
        UIColor.red.setFill()
        // width value determines how much we will fill
        ctx.fill(CGRect(x: 0, y: 0, width: sz.width/2, height: sz.height))
    }
    let result = UIGraphicsImageRenderer(size:sz).image {
        ctx in
        green.draw(at: .zero)
        red.draw(at: .zero, blendMode: .sourceAtop, alpha: 1)
    }

enter image description here

Just vary the value at width: sz.width/2 for different "thermometer" amounts.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • Hey thanks for your answer. Looks like a working solution for iOS but unfortunately UIGraphicsImageRenderer is not available on watchOS. – stacj Jan 26 '20 at 00:06
  • Yeah, sorry about that! But it shows you that this is the right blend mode, which is what you really needed to know. – matt Jan 26 '20 at 00:16
2

Based on @matt's answer using .sourceAtop I found a working solution for WatchKit:

let size = CGSize(width: 300, height: 100)
UIGraphicsBeginImageContext(size)

let context = UIGraphicsGetCurrentContext()!
let count = 5;
let padding = (size.width / CGFloat(count)) * 0.5
for i in 0...count {
    let offsetX = size.width * (CGFloat(i) / CGFloat(count))
    let rect = CGRect(x: offsetX + padding/2, y: 0, width: padding, height: size.height)
    context.setFillColor(UIColor.green.cgColor)
    context.fill(rect)
}

context.setFillColor(UIColor.red.cgColor)
context.setBlendMode(CGBlendMode.sourceAtop)
context.fill(CGRect(x: 0.0, y: 0.0, width: size.width * 0.5, height: size.height))

UIGraphicsEndImageContext()
stacj
  • 1,103
  • 1
  • 7
  • 20