iPhone iOS Generate star, sunburst or polygon UIBezierPath programmatically
Asked Answered
S

4

7

I'm looking for a way to programmatically create stars, sunburst, and other "spiky" effects using UIBezierPath.

Starburst image

UIBezierPath *sunbeamsPath = [UIBezierPath bezierPath];
[sunbeamsPath moveToPoint: CGPointMake(x, y)];

Are there any algorithms that can generate points for sunburst like shapes programmatically, without paths overlapping?

I'm also interested in an irregular shape sunburst like the one below:

irregular sunburst

I would imagine that such algorithm would take a certain number of rays, then roughly divide the circle in a number of segments and generate points for such segment in a clockwise direction. Does an algorithm like the one I'm describing already exists or will I have to write one by myself?

Thank you!

Sherburne answered 1/5, 2012 at 15:26 Comment(0)
S
7

I know this old, but I was curious about the first part of this question myself, and going off jrturton's post, I created a custom UIView that generates a UIBezierPath from center of the view. Even animated it spinning for bonus points. Here is the result:

enter image description here

The code I used is here:

- (void)drawRect:(CGRect)rect {
CGFloat radius = rect.size.width/2.0f;

[self.fillColor setFill];
[self.strokeColor setStroke];

UIBezierPath *bezierPath = [UIBezierPath bezierPath];
CGPoint centerPoint = CGPointMake(rect.origin.x + radius, rect.origin.y + radius);

CGPoint thisPoint = CGPointMake(centerPoint.x + radius, centerPoint.y);
[bezierPath moveToPoint:centerPoint];

CGFloat thisAngle = 0.0f;
CGFloat sliceDegrees = 360.0f / self.beams / 2.0f;

for (int i = 0; i < self.beams; i++) {

    CGFloat x = radius * cosf(DEGREES_TO_RADIANS(thisAngle + sliceDegrees)) + centerPoint.x;
    CGFloat y = radius * sinf(DEGREES_TO_RADIANS(thisAngle + sliceDegrees)) + centerPoint.y;
    thisPoint = CGPointMake(x, y);
    [bezierPath addLineToPoint:thisPoint];
    thisAngle += sliceDegrees;

    CGFloat x2 = radius * cosf(DEGREES_TO_RADIANS(thisAngle + sliceDegrees)) + centerPoint.x;
    CGFloat y2 = radius * sinf(DEGREES_TO_RADIANS(thisAngle + sliceDegrees)) + centerPoint.y;
    thisPoint = CGPointMake(x2, y2);
    [bezierPath addLineToPoint:thisPoint];
    [bezierPath addLineToPoint:centerPoint];
    thisAngle += sliceDegrees;

}

[bezierPath closePath];

bezierPath.lineWidth = 1;
[bezierPath fill];
[bezierPath stroke];

}

And you can download a sample project here:

https://github.com/meekapps/Sunburst

Shiprigged answered 20/2, 2013 at 22:38 Comment(0)
M
2

I'm not aware of an algorithm to create these but I do have some advice - create your bezier path such that (0,0) is the centre of the sunburst, then define however many points you need to draw one "beam" of your sunburst going upwards, returning to (0,0)

Then, for as many beams as you want, perform a loop: apply a rotation transform (2 pi / number of beams) to your sunbeam points (CGPointApplyTransform), and add them to the path.

Once you are finished, you can translate and scale the path for drawing.

I used a similar process to draw star polygons recently and it was very simple. Credit to Rob Napier's book for the idea.

Maggie answered 1/5, 2012 at 15:54 Comment(0)
G
0

Swift version for this

import UIKit

extension Int {
    var degreesToRadians: Double { return Double(self) * .pi / 180 }
    var radiansToDegrees: Double { return Double(self) * 180 / .pi }
}
extension FloatingPoint {
    var degreesToRadians: Self { return self * .pi / 180 }
    var radiansToDegrees: Self { return self * 180 / .pi }
}

class SunBurstView: UIView {

    override func draw(_ rect: CGRect) {
        let radius: CGFloat = rect.size.width / 2.0
        UIColor.red.setFill()
        UIColor.blue.setStroke()
        let bezierPath = UIBezierPath()
        let centerPoint = CGPoint(x: rect.origin.x + radius, y: rect.origin.y + radius)
        var thisPoint = CGPoint(x: centerPoint.x + radius, y: centerPoint.y)
        bezierPath.move(to: centerPoint)
        var thisAngle: CGFloat = 0.0
        let sliceDegrees: CGFloat = 360.0 / self.beams / 2.0

        for _ in 0..<self.beams {
            let x = radius * CGFloat(cosf(Float((thisAngle + sliceDegrees).degreesToRadians))) + centerPoint.x
            let y = radius * CGFloat(sinf(Float((thisAngle + sliceDegrees).degreesToRadians))) + centerPoint.y
            thisPoint = CGPoint(x: x, y: y)
            bezierPath.addLine(to: thisPoint)
            thisAngle += sliceDegrees
            let x2 = radius * CGFloat(cosf(Float((thisAngle + sliceDegrees).degreesToRadians))) + centerPoint.x
            let y2 = radius * CGFloat(sinf(Float((thisAngle + sliceDegrees).degreesToRadians))) + centerPoint.y
            thisPoint = CGPoint(x: x2, y: y2)
            bezierPath.addLine(to: thisPoint)
            bezierPath.addLine(to: centerPoint)
            thisAngle += sliceDegrees
        }
        bezierPath.close()
        bezierPath.lineWidth = 1
        bezierPath.fill()
        bezierPath.stroke()
    }

}
Gourde answered 26/8, 2017 at 3:16 Comment(0)
C
0

I noticed that the Swift version didn't compile for me or take up enough of the screen, so here's Reinier's answer in Swift 4 adjusted for a rectangular view.

extension Int {
    var degreesToRadians: Double { return Double(self) * .pi / 180 }
    var radiansToDegrees: Double { return Double(self) * 180 / .pi }
}

extension FloatingPoint {
    var degreesToRadians: Self { return self * .pi / 180 }
    var radiansToDegrees: Self { return self * 180 / .pi }
}

class SunBurstView: UIView {

    var beams: CGFloat = 20

    override func draw(_ rect: CGRect) {
        self.clipsToBounds = false
        self.layer.masksToBounds = false
        let radius: CGFloat = rect.size.width * 1.5
        UIColor.orange.withAlphaComponent(0.3).setFill()
        UIColor.clear.setStroke()
        let bezierPath = UIBezierPath()
        let centerPoint = CGPoint(x: rect.origin.x + (radius / 3), y: rect.origin.y + (radius / 1.5))
        var thisPoint = CGPoint(x: centerPoint.x + radius, y: centerPoint.y)

        bezierPath.move(to: centerPoint)
        var thisAngle: CGFloat = 0.0
        let sliceDegrees: CGFloat = 360.0 / self.beams / 2.0

        for _ in 0...Int(beams) {
            let x = radius * CGFloat(cosf(Float((thisAngle + sliceDegrees).degreesToRadians))) + centerPoint.x
            let y = radius * CGFloat(sinf(Float((thisAngle + sliceDegrees).degreesToRadians))) + centerPoint.y
            thisPoint = CGPoint(x: x, y: y)
            bezierPath.addLine(to: thisPoint)
            thisAngle += sliceDegrees
            let x2 = radius * CGFloat(cosf(Float((thisAngle + sliceDegrees).degreesToRadians))) + centerPoint.x
            let y2 = radius * CGFloat(sinf(Float((thisAngle + sliceDegrees).degreesToRadians))) + centerPoint.y
            thisPoint = CGPoint(x: x2, y: y2)
            bezierPath.addLine(to: thisPoint)
            bezierPath.addLine(to: centerPoint)
            thisAngle += sliceDegrees
        }
        bezierPath.close()
        bezierPath.lineWidth = 1
        bezierPath.fill()
        bezierPath.stroke()
    }

}

enter image description here

Carlinecarling answered 12/8, 2019 at 21:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.