Creating progress circle as WKInterfaceImage in Watch App

10,568

Solution 1

As of watchOS 3 it is possible to use SpriteKit.

SKShapeNode can draw shapes, so it is possible to create radial rings.

  1. Add a WKInterfaceSKScene to your interface controller in storyboard and hook it up through an outlet.
  2. Set it up in the awake method of the interface controller

    override func awake(withContext context: Any?) {
       super.awake(withContext: context)
       scene = SKScene(size: CGSize(width: 100, height: 100))
       scene.scaleMode = .aspectFit
       interfaceScene.presentScene(scene)
    }
    
  3. Create a shape node and add it to the scene

    let fraction: CGFloat = 0.75
    let path = UIBezierPath(arcCenter: .zero,
                             radius: 50,
                             startAngle: 0,
                             endAngle: 2 * .pi * fraction,
                             clockwise: true).cgPath
    
    let shapeNode = SKShapeNode(path: path)
    shapeNode.strokeColor = .blue
    shapeNode.fillColor = .clear
    shapeNode.lineWidth = 4
    shapeNode.lineCap = .round
    shapeNode.position = CGPoint(x: scene.size.width / 2, y: scene.size.height / 2)
    scene.addChild(shapeNode)
    

Example

Solution 2

Another solution is to create 100 picture, for each number you have a frame. So, doing that, you can show an animation using 'startAnimatingWithImagesInRange:duration:repeatCount' method.

The problem is that it's hard to customise each frame. Somebody thought about this issue and created a generator. You can find it by this name:

Radial Bar Chart Generator

This link should help you to customise the frames: http://hmaidasani.github.io/RadialChartImageGenerator/

Also you have same samples on git link. For 100 frames with a single arc frame you get around 1,8 MB on disk.

Solution 3

Most of what is available on iOS is not present (yet) in WatchKit. In particular, several of the things you want to do are almost impossible. (Glimmer of hope in a moment). In particular, you cannot rotate an image. Or rather, you can rotate an image, but you have to do it on the phone and then pass that image up to the watch at runtime. Also, you cannot easily composite images - however, there is a way to do it.

One way would be to construct the entire rotated, composited image the way you want it on the phone and pass the final data up to the button using [WKInterfaceButton setBackgroundImage:]. Unfortunately, you will likely find this to be slow in the simulator, and most likely it will work poorly on the actual watch. Hard to know for sure because we don't have one, but this is sending the image on the fly over Bluetooth. So you won't get smooth animation or good response times.

A better way is to hack your way to it on the watch. This relies on two tricks: one, layering groups together with background images. Two, using -[WKInterfaceImage startAnimatingWithImagesInRange:duration:repeatCount:].

For the first trick, drop a Group into your layout, then put another group inside it, then (possibly) a button inside that. Then use -[WKInterfaceGroup setBackgroundImage:] and the images will composite together. Make sure you use proper transparency, etc.

For the second trick, refer to the official documentation - essentially, you will need a series of images, one for each possible rotation value, as erdekhayser said. Now, this may seem egregious (it is) and possibly impractical (it is not). This is actually how Apple recommends creating spinners and the like - at least for now. And, yes, that may mean generating 360 different images, although because of the small screen, my advice is to go every 3-5 degrees or so (nobody will be able to tell the difference).

Hope this helps.

Solution 4

Nobody posts code??? Here it is! enjoy:

1) Create the images here: http://hmaidasani.github.io/RadialChartImageGenerator/

2) Drag n Drop a picker into your View Controller, and link it to some viewController.swift file. Set the Picker style to "Sequence" on the menu that appears on the right.

3) Add this code to the viewController.swift , and connect the picker to the IBOutlet and the IBAction:

import WatchKit
import Foundation

class PickerViewController: WKInterfaceController {

    override func awakeWithContext(context: AnyObject?) {
        super.awakeWithContext(context)
    }

    @IBOutlet var itemPicker: WKInterfacePicker!

    override func willActivate() {
        super.willActivate()

        let pickerItems: [WKPickerItem] = (0...100).map {
            let pickerItem = WKPickerItem()
            pickerItem.contentImage = WKImage(imageName: "singleArc\($0).png")
            return pickerItem
        }
        itemPicker.setItems(pickerItems)
    }

    @IBAction func pickerSelectedItemChanged(value: Int) {
        NSLog("Sequence Picker: \(value) selected.")
    }

    override func didDeactivate() {
        super.didDeactivate()
    }

}

Solution 5

WKInterface classes are not able to be subclassed. Therefore, a custom control is not possible.

Also, animation is limited. In order to create an animation, you must store every single frame as an image. Then, you can have an image view in your WatchKit app that cycles through these images.

Store the images in the Images.xcassets folder in the watch target, and try to mess around with the changing the frame based on the percentage the activity is finished.

One extra note: having 100 images would not be efficient, as each WatchKit app has only a limited amount of space on the watch it can take up (I believe it is 20MB, but I am not sure). Maybe have an image for every 5%.

Share:
10,568
user3746428
Author by

user3746428

Updated on July 29, 2022

Comments

  • user3746428
    user3746428 almost 2 years

    I am trying to create a progress circle for the Apple Watch version of my app. I know that we aren't able to use UIViews (which would make things so much easier!) so I am looking for alternatives.

    Basically, I would like to create one of these prototypes:

    enter image description here

    The way I was hoping to do things was to add the background layers as a normal WKInterfaceImage and then the progress arrow/line on top as a WKInterfaceImage that rotates around the circle based on the percentage calculated.

    I have the percentage calculated so basically, what I am looking for is some help with the math code for rotating the arrow.

    Does anyone know if this is possible, and could anyone help me out if so? I'm not trying to update the circle while the app is running; it just needs to update when the Watch App launches to correspond with the iOS version.

    Thanks!

  • user3746428
    user3746428 over 9 years
    Thanks for the response. I didn't make it entirely clear, but what I intended to do was to rotate a single WKInterfaceImage of an arrow around 360 degrees. So assuming there is a way to rotate images in code, then this should work? I didn't have any plans for animating the image, so that's fine. As long as it is able to rotate to the right position when the app is launched then that's what I would like it to do. Also, I think the memory limit is 20mb.
  • Jano
    Jano about 9 years
    @user3746428 You can’t rotate a WKInterfaceImage. You can rotate a UIImage in the extension and send it, or better yet, pregenerate all the frames, store them in the watch app bundle and send updates.
  • JoeGalind
    JoeGalind over 5 years
    Remeber to import SpriteKit
  • Tap Forms
    Tap Forms about 5 years
    I find that it takes a second or two, even on the Apple Watch Series 4 to display the SKScene. It's an annoying delay that makes me not want to use this technique unfortunately, although I'd much rather do it this way than a sequence of images to animate.
  • Tap Forms
    Tap Forms about 5 years
    Oh, and just to clarify, it's only the first time there's a big delay in getting going. I guess the watch is loading up the SpriteKit framework.