Render a line graph on Apple Watch using watchOS 2

I am trying to render a line/step graph on Apple Watch using watchOS 2. Unlike iOS 9, watchOS 2 doesn’t support Quartz. It only supports Core Graphics. I tried writing some code to draw a line graph but I am getting an error “CGContextRestoreGState: invalid context 0x0. This is a serious error. This application, or a library it uses, is using an invalid context and is thereby contributing to an overall degradation of system stability and reliability. This notice is a courtesy: please fix this problem. It will become a fatal error in an upcoming update.”

Following is the piece of code I used:

  • Adding drag and drop component to iOS app
  • Xcode 5.0.1 iOS 7.0.3 Simulator Not launching
  • xcode 9 crash when open storyboard
  • Passing NSMutableArray from Modal View Controller to Parent View
  • iOS9 popover always points to top-left corner of anchor
  • #import <libxml/tree.h> file not found after xcode update
  • import WatchKit
    import Foundation
    import UIKit
    
    class InterfaceController: WKInterfaceController{
        override func awakeWithContext(context: AnyObject?) {
            super.awakeWithContext(context)
            let path = UIBezierPath()
            let startPoint =  CGPointMake(0.0, 0.0)
            path.moveToPoint(startPoint)
            let nextPoint = CGPointMake(20.0, 20.0)
            path.addLineToPoint(nextPoint)
            path.lineWidth = 1.0
            UIColor.whiteColor().setStroke()
            path.stroke()
        }
    
        override func willActivate() {
            super.willActivate()
    
        }
    
        override func didDeactivate() {
            super.didDeactivate()
        }
    }
    

    My end result should be something like Stocks app present on Apple Watch. Wwhenever user clicks on particular stock, he will be able to view/visualize the statistics of that stock. Can anybody please help me in achieving this.

    5 Solutions Collect From Internet About “Render a line graph on Apple Watch using watchOS 2”

    I succeeded to render lines with following steps:

    • Create a bitmap-based graphics context and makes it the current context using UIGraphicsBeginImageContext.
    • Draw into the context.
    • Extract CGImageRef from the context and convert it to UIImage object.
    • Show the image on WKInterfaceGroup or WKInterfaceImage.

    Code:

    // Create a graphics context
    let size = CGSizeMake(100, 100)
    UIGraphicsBeginImageContext(size)
    let context = UIGraphicsGetCurrentContext()
    
    // Setup for the path appearance
    CGContextSetStrokeColorWithColor(context, UIColor.whiteColor().CGColor)
    CGContextSetLineWidth(context, 4.0)
    
    // Draw lines
    CGContextBeginPath (context);
    CGContextMoveToPoint(context, 0, 0);
    CGContextAddLineToPoint(context, 100, 100);
    CGContextMoveToPoint(context, 0, 100);
    CGContextAddLineToPoint(context, 100, 0);
    CGContextStrokePath(context);
    
    // Convert to UIImage
    let cgimage = CGBitmapContextCreateImage(context);
    let uiimage = UIImage(CGImage: cgimage!)
    
    // End the graphics context
    UIGraphicsEndImageContext()
    
    // Show on WKInterfaceImage
    image.setImage(uiimage)
    

    image is WKInterfaceImage property. It works for me.

    Also I can draw using UIBezierPath on watchOS as follow:

    // Create a graphics context
    let size = CGSizeMake(100, 100)
    UIGraphicsBeginImageContext(size)
    let context = UIGraphicsGetCurrentContext()
    UIGraphicsPushContext(context!)
    
    // Setup for the path appearance
    UIColor.greenColor().setStroke()
    UIColor.whiteColor().setFill()
    
    // Draw an oval
    let rect = CGRectMake(2, 2, 96, 96)
    let path = UIBezierPath(ovalInRect: rect)
    path.lineWidth = 4.0
    path.fill()
    path.stroke()
    
    // Convert to UIImage
    let cgimage = CGBitmapContextCreateImage(context);
    let uiimage = UIImage(CGImage: cgimage!)
    
    // End the graphics context
    UIGraphicsPopContext()
    UIGraphicsEndImageContext()
    
    image.setImage(uiimage)
    

    You can check the sample codes here.

    Here’s code in watchOS 3

      // Create a graphics context
            let size = CGSize(width:self.contentFrame.size.width, height:100)
    
    
            UIGraphicsBeginImageContext(size)
            let context = UIGraphicsGetCurrentContext()
    
            // Setup for the path appearance
            context!.setStrokeColor(UIColor.white.cgColor)
            context!.setLineWidth(4.0)
    
            // Draw lines
            context!.beginPath ();
    
            context?.move(to: CGPoint())
            context?.addLine(to: CGPoint(x: 100, y: 100))
            context?.addLine(to: CGPoint(x: 0, y: 100))
            context?.addLine(to: CGPoint(x: 100, y: 0))
    
            context!.strokePath();
    
            // Convert to UIImage
            let cgimage = context!.makeImage();
            let uiimage = UIImage(cgImage: cgimage!)
    
            // End the graphics context
            UIGraphicsEndImageContext()
    
            self.graphGroup.setBackgroundImage(uiimage)
    

    The context here are not CGContext, thus you have the issue. I do not think you can use Core Graphics directly on Apple Watch.

    Here’s the example project that shows how to do dynamic graphs on WatchKit 2.0

    https://github.com/vlm/ExampleWatchGraph

    The gist of it is in the GraphPainter.swift file, where CoreGraphics is used to draw into an off-screen buffer (as @shu223 suggested), then display this buffer in a WKInterfaceImage.

    Animated Watch Interface GIF.

    Graph render is done by the help of bezierPath and which is converted to image to attached to Apple Watch

    //Swift-3 Xcode-8.1

    import UIKit
    class InterfaceController: WKInterfaceController {

    @IBOutlet var graphImage: WKInterfaceImage!
    override func awake(withContext context: Any?) {
        super.awake(withContext: context)
    
        // Configure interface objects here.
    }
    override func willActivate() {
    
        super.willActivate()
        let path = createBeizePath()
    
        //Change graph to image
        let image:UIImage = UIImage.shapeImageWithBezierPath(bezierPath: path, fillColor: .red, strokeColor: .black)
        graphImage.setImage(uiimage)
    }
    
    //Draw graph here
    func createBeizePath() -> UIBezierPath
    {
        let path = UIBezierPath()
        //Rectangle path Trace
        path.move(to: CGPoint(x: 20, y: 100) )
        path.addLine(to: CGPoint(x: 50 , y: 100))
        path.addLine(to: CGPoint(x: 50, y: 150))
        path.addLine(to: CGPoint(x: 20, y: 150))
        return path
      }
    }
    
    extension UIImage {
            class func shapeImageWithBezierPath(bezierPath: UIBezierPath, fillColor: UIColor?, strokeColor: UIColor?, strokeWidth: CGFloat = 0.0) -> UIImage! {
                bezierPath.apply(CGAffineTransform(translationX: -bezierPath.bounds.origin.x, y: -bezierPath.bounds.origin.y ) )
                let size = CGSize(width: 100    , height: 100)
                UIGraphicsBeginImageContext(size)
                let context = UIGraphicsGetCurrentContext()
                var image = UIImage()
                if let context  = context {
                    context.saveGState()
                    context.addPath(bezierPath.cgPath)
                    if strokeColor != nil {
                        strokeColor!.setStroke()
                        context.setLineWidth(strokeWidth)
                    } else { UIColor.clear.setStroke() }
                    fillColor?.setFill()
                    context.drawPath(using: .fillStroke)
                     image = UIGraphicsGetImageFromCurrentImageContext()!
                    context.restoreGState()
                    UIGraphicsEndImageContext()
                }
                return image
            }
        }