0

I am very new to Swift's CoreGraphics, so there is likely a lot of things I'm doing wrong here, but I couldn't find a question like mine, so I decided to post my own.

I am using CoreGraphics to draw circles in specific locations on the screen. When I simply use cgContext.addEllipse(in: rect), I can render up to a hundred circles no-problem. However, I found a quick radial gradient code block online that can add a 3D effect to the otherwise 2D circles. The following code is that code block, modified by myself to apply to my application:

func drawSphere(in rect: CGRect, withColor color: CGColor, withContext ctx: CGContext) {
    let locations: [CGFloat] = [0.0, 1.0]

    let colors = [UIColor.white.cgColor, color]

    let colorspace = CGColorSpaceCreateDeviceRGB()

    let gradient = CGGradient(colorsSpace: colorspace,
                              colors: colors as CFArray, locations: locations)

    var startPoint = CGPoint()
    var endPoint = CGPoint()
    startPoint.x = rect.minX
    startPoint.y = rect.minY
    endPoint.x = rect.minX + rect.width / 4
    endPoint.y = rect.minY + rect.height / 4
    let startRadius: CGFloat = 0
    let endRadius: CGFloat = rect.width / 2.0

    ctx.drawRadialGradient (gradient!, startCenter: startPoint,
                                 startRadius: startRadius, endCenter: endPoint, 
                                 endRadius: endRadius,
                                 options: .drawsBeforeStartLocation)
}

This works well, visually. However, my device slows to a crawl, struggling to even render a single sphere with this method. When I drag around on the screen, the spheres are supposed to move along with my drags, but when I use the above rendering code, I see a very significant drop in frames.

Here is the context from which the above function is called:

func drawPlanet(_ planet: Planet, withContext ctx: CGContext) {

    let distance = MathHelper.hypot3(x: planet.x, y: planet.y, z: flatDistance + planet.z)

    let angularDiameter = MathHelper.angularDiameter(diameter: planet.diameter, distance: distance)
    let angularX = MathHelper.angularDiameter(diameter: planet.x, distance: distance)
    let angularY = MathHelper.angularDiameter(diameter: planet.y, distance: distance)
    let fieldOfView = MathHelper.toRadians(fov)

    let pitchRad = MathHelper.toRadians(pitch)
    let yawRad = MathHelper.toRadians(yaw)
    let pitchedY = distance * tan(pitchRad)
    let yawedX = distance * tan(yawRad)

    let adjustedDiameter = Double(frame.width) * angularDiameter / fieldOfView
    let adjustedX = Double(frame.width) * angularX / fieldOfView
    let adjustedY = Double(frame.width) * angularY / fieldOfView

    let middleX = Double(frame.width) / 2.0 + yawedX
    let middleY = Double(frame.height) / 2.0 + pitchedY

    let rect = CGRect(x: middleX - adjustedX - adjustedDiameter / 2, y: middleY - adjustedY - adjustedDiameter / 2, width: adjustedDiameter, height: adjustedDiameter)

    if (rect.minX < frame.maxX || rect.maxX > frame.minX) && (rect.minY < frame.maxY || rect.maxY > frame.minY) {
        ctx.beginPath()

        ctx.setLineWidth(1)
        ctx.setStrokeColor(planet.color.cgColor)
        ctx.setFillColor(planet.color.cgColor)

        drawSphere(in: rect, withColor: planet.color.cgColor, withContext: ctx)

        if planet.selected {
            ctx.beginPath()

            ctx.setLineWidth(2)
            ctx.setStrokeColor(UIColor.white.cgColor)

            ctx.addRect(rect)
            ctx.drawPath(using: .stroke)
        }

        //ctx.closePath()
    }
}

And here is the context from which that function is called:

override func draw(_ rect: CGRect) {
    guard let ctx = UIGraphicsGetCurrentContext() else { return }

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

    for planet in currentGalaxy.planets {
        drawPlanet(planet, withContext: ctx)
    }
}

I am not constantly redrawing the screen. I am only calling view.setNeedsDisplay() whenever the device changes its orientation or a UIGestureRecognizer is triggered.

I imagine there is some glaring inefficiency in my code somewhere, but I do not know where that inefficiency lies. Any help would be greatly appreciated. Thank you!

  • Did you tried `Instruments` to find the culprit? – Xvolks Sep 01 '17 at 19:21
  • @Xvolks I'm afraid I don't know what you mean. Like I said, I'm very new to Swift and CoreGraphics, so I don't have much experience debugging or anything like that. When I check the debug tab, the CPU meter jumps from 0% to 100% when I drag or zoom or anything like that, if that's what you mean. Sorry I couldn't really answer your question... – elektrikpulse61 Sep 01 '17 at 19:24
  • In Xcode click `Product` then `Profile`, this will launch Instruments. Choose the profiling template (you may start by Counters, it you find nothing you may try Core Animation). – Xvolks Sep 01 '17 at 19:35
  • @Xvolks Thank you. I was able to launch the `Instruments`, and I was able to record a run on my iPhone 7 Plus (with the same frame rate issues). What should I do with the recorded run? Is there something in the data that I should be looking for? – elektrikpulse61 Sep 01 '17 at 19:49
  • Start by searching for the time spent in each method of your code. – Xvolks Sep 01 '17 at 19:51
  • @Xvolks I found that 89.5% of the time is spent in the `draw()` function, 88.2% in the `drawPlanet()` function, 88.1% in the `drawSphere()` function, and 88.0% in the `CGContextDrawRadialGradient()` function. So basically almost 90% of the time was spent in the CoreGraphics method, not one of my methods. Perhaps I need to call that function less often? – elektrikpulse61 Sep 01 '17 at 19:56
  • You may look at https://stackoverflow.com/questions/4425635/drawrect-speed-and-cgcontextdrawradialgradient-super-slow – Xvolks Sep 01 '17 at 19:58
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/153497/discussion-between-elektrikpulse61-and-xvolks). – elektrikpulse61 Sep 01 '17 at 20:00

0 Answers0