Search code examples
iosswiftcore-graphicscgcontext

CGContext not drawing after initialising


I am trying to create own CGContext in UIView draw(_:). Here:

    override func draw(_ rect: CGRect) {
        //let ctx = UIGraphicsGetCurrentContext()
        let width = Int(rect.size.width)
        let height = Int(rect.size.height)
        let bytesPerPixel = 4
        let bytesPerRow = bytesPerPixel * width
        let data = UnsafeMutableRawPointer.allocate(byteCount: bytesPerRow * height/*727080*/, alignment: 8)
        let colorSpace = CGColorSpaceCreateDeviceRGB()
        guard let ctx = CGContext(data: data, width: width, height: height, bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue) else {
            return
        }
        ctx.setStrokeColor(UIColor.orange.cgColor)
        ctx.setLineWidth(3.0)
        ctx.stroke(rect)
    }

And the view is not outlined on display whereas using the current context works naturally (displays bounds border stroke):

    override func draw(_ rect: CGRect) {
        guard let ctx = UIGraphicsGetCurrentContext() else {
            return
        }
        ctx.setStrokeColor(UIColor.orange.cgColor)
        ctx.setLineWidth(3.0)
        ctx.stroke(rect)
    }

I have followed the description in the swift header file for the CGContext init method. What is the reason this would function differently than grabbing the current context as I seem to use the bitmap info set from the current view.


Solution

  • Here is a simple demo of creating offscreen content for preparing something (might be heavy, so not blocking UI) to present in view

    demo

    class ViewWithOffscreenDrawing: UIView {
        private var offscreenImage: CGImage? = nil
    
        override func draw(_ rect: CGRect) {
            if offscreenImage == nil { // image not ready
                DispatchQueue.global(qos: .background).async { // draw offscreen
                    let image = renderSomething(of: rect.size)
                    DispatchQueue.main.async { [weak self] in
                        self?.offscreenImage = image
                        self?.setNeedsDisplay()
                    }
                }
                return
            }
    
            // image has prepared - just draw on screen
            UIGraphicsGetCurrentContext()?.draw(offscreenImage!, in: rect)
        }
    }
    
    func renderSomething(of size: CGSize) -> CGImage? {
        let bitsPerComponent = 8
        let bytesPerRow = Int(size.width) * 4
        let colorSpace = CGColorSpace(name: CGColorSpace.genericRGBLinear)!
    
        let context = CGContext(data: nil, width: Int(size.width), height: Int(size.height),
            bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow,
            space: colorSpace, bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue)
    
        // >>> draw anything heavy in background context
    
        context?.setFillColor(gray: 1, alpha: 1)
        context?.fill(CGRect(origin: .zero, size: size))
        context?.setFillColor(UIColor.red.cgColor)
        context?.fillEllipse(in: CGRect(origin: .zero, size: size))
    
        // <<<
    
        return context?.makeImage() // generated result
    }