How to mark points on a path drawn using CAShapeLayer - Swift? - ios

I have a custom path drawn using CAShapeLayer in a UIView. Its a semicircle, upper half, drawn from left to right.
Following is the code used, writted in a UIView subclass:
func drawSemicircle() {
// drawing an upper half of a circle -> 180 degree to 0 degree, clockwise
let startAngle = CGFloat(M_PI)
let endAngle = CGFloat(0.0)
let centerPoint = CGPointMake(CGRectGetWidth(frame)/2 , CGRectGetHeight(frame))
// path set here
let semirCircleLayer = CAShapeLayer()
let semirCirclePath = UIBezierPath(arcCenter:centerPoint, radius: CGRectGetWidth(frame)/2 - 20.0 , startAngle:startAngle, endAngle:endAngle, clockwise: true)
semirCircleLayer.path = semirCirclePath.CGPath
// layer customisation
semirCircleLayer.fillColor = UIColor.clearColor().CGColor
semirCircleLayer.strokeColor = UIColor(red: 237.0/255.0, green: 236.0/255.0, blue: 236.0/255.0, alpha: 1.0).CGColor
semirCircleLayer.lineWidth = 20.0
semirCircleLayer.lineCap = kCALineCapButt
layer.addSublayer(semirCircleLayer)
}
Current path looks like this:
What I am tryign to implement is to mark some points on the bar .Like a analog clock graduation + diigts, but not so complex. Just mark 1 point at any progress level. So a final output, something like this :
Can I get some help on this?

You'll want to use another CAShapeLayer for the graduation line and a CATextLayer for the text at the end of the graduation.
Instead of doing Core Graphics drawing as my answer here does, I recommend you use a 100% layer approach as to best fit in with your existing code.
Something like this should do the trick:
func drawSemicircle() {
// drawing an upper half of a circle -> 180 degree to 0 degree, clockwise
let startAngle = CGFloat(M_PI)
let endAngle = CGFloat(0.0)
let centerPoint = CGPoint(x: CGRectGetWidth(frame)*0.5 , y: CGRectGetHeight(frame))
let radius = frame.size.width*0.5 - 80.0 // radius of your arc
// path set here
let semiCircleLayer = CAShapeLayer()
semiCircleLayer.frame = bounds // requried for layer calculations
let semiCirclePath = UIBezierPath(arcCenter:centerPoint, radius:radius, startAngle:startAngle, endAngle:endAngle, clockwise: true)
semiCircleLayer.path = semiCirclePath.CGPath
// layer customisation
semiCircleLayer.fillColor = UIColor.clearColor().CGColor
semiCircleLayer.strokeColor = UIColor(red: 237.0/255.0, green: 236.0/255.0, blue: 236.0/255.0, alpha: 1.0).CGColor
semiCircleLayer.lineWidth = 20.0
semiCircleLayer.lineCap = kCALineCapButt
layer.addSublayer(semiCircleLayer)
// draw graduation (cue the wall of code!)
let graduationLayer = CAShapeLayer() // the graduation layer that'll display the graduation
graduationLayer.frame = semiCircleLayer.bounds
let graduationWidth = CGFloat(4.0) // the width of the graduation
let graduationLength = CGFloat(50.0) // the length of the graduation
let graduationColor = UIColor.redColor() // the color of both the graduation line and text
let startGradRad = radius-semiCircleLayer.lineWidth*0.5 // the starting radius of the graduation
let endGradRad = startGradRad+graduationLength // the ending radius of the graduation
let graduationAngle = CGFloat(M_PI*0.79) // 21% along the arc from the left (0 degrees coresponds to the right hand side of the circle, with the positive angle direction going anti-clocwise (much like a unit circle in maths), so we define 79% along the arc, from the right hand side)
// the starting point of the graduation line. the angles are negative as the arc is effectively drawn upside-down in the UIKit coordinate system.
let startGradPoint = CGPoint(x: cos(-graduationAngle)*startGradRad+centerPoint.x, y: sin(-graduationAngle)*startGradRad+centerPoint.y)
let endGradPoint = CGPoint(x: cos(-graduationAngle)*endGradRad+centerPoint.x, y: sin(-graduationAngle)*endGradRad+centerPoint.y)
// the path for the graduation line
let graduationPath = UIBezierPath()
graduationPath.moveToPoint(startGradPoint) // start point
graduationPath.addLineToPoint(endGradPoint) // end point
graduationLayer.path = graduationPath.CGPath // add path to the graduation shape layer
// configure stroking options
graduationLayer.fillColor = UIColor.clearColor().CGColor
graduationLayer.strokeColor = graduationColor.CGColor
graduationLayer.lineWidth = graduationWidth
// add to semi-circle layer
semiCircleLayer.addSublayer(graduationLayer)
// the font of the text to render at the end of the graduation
let textFont = UIFont.systemFontOfSize(30)
// the text to render at the end of the graduation - do you custom value logic here
let str : NSString = "value"
// default paragraph style
let paragraphStyle = NSParagraphStyle()
// the text attributes dictionary. used to obtain a size of the drawn text in order to calculate its frame
let textAttributes = [NSParagraphStyleAttributeName:paragraphStyle, NSFontAttributeName:textFont]
// size of the rendered text
let textSize = str.sizeWithAttributes(textAttributes)
let xOffset = abs(cos(graduationAngle))*textSize.width*0.5 // the x-offset of the text from the end of the graduation line
let yOffset = abs(sin(graduationAngle))*textSize.height*0.5 // the y-offset of the text from the end of the graduation line
/// the padding between the graduation line and the text
let graduationTextPadding = CGFloat(5.0)
// bit of pythagorus to determine how far away the center of the text lies from the end of the graduation line. multiplying the values together is cheaper than using pow. the text padding is added onto it.
let textOffset = sqrt(xOffset*xOffset+yOffset*yOffset)+graduationTextPadding
// the center of the text to render
let textCenter = CGPoint(x: cos(-graduationAngle)*textOffset+endGradPoint.x, y: sin(-graduationAngle)*textOffset+endGradPoint.y)
// the frame of the text to render
let textRect = CGRect(x: textCenter.x-textSize.width*0.5, y: textCenter.y-textSize.height*0.5, width: textSize.width, height: textSize.height)
let textLayer = CATextLayer()
textLayer.contentsScale = UIScreen.mainScreen().scale // to ensure the text is rendered at the screen scale
textLayer.frame = textRect
textLayer.string = str
textLayer.font = textFont
textLayer.fontSize = textFont.pointSize // required as CATextLayer ignores the font size of the font you pass
textLayer.foregroundColor = graduationColor.CGColor // color of text
graduationLayer.addSublayer(textLayer)
}
Output:
There's quite a lot of code here, as you have to do some extra logic in order to calculate the size of the CATextLayer. You could simplify this by using a UILabel as you can use sizeToFit in order to calculate the size, however this may complicate the layer hierarchy.
I've tried my best to explain each line of code - but if you still have questions, I'll be happy to answer them!
Furthermore, if you re-structure the code, you could easily allow for the graduation angle, as well as the other variables to be changed from outside the class. I've provided an example of this in the full project below.
Full Project: https://github.com/hamishknight/Dial-View

Related

iOS, how to continuously animate a line "running" ("marching ants" effect)?

I must admit I have no clue how to do this in iOS -
Here's some code that makes a nice dotted line:
Now, I want that line to "run" upwards:
So, every one second it will move upwards by, itemLength * 2.0.
Of course, it would wrap around top to bottom.
So, DottedVertical should just do this completely on its own.
Really, how do you do this in iOS?
It would be great if the solution is general and will "scroll" any I suppose layer or drawn thing.
In say a game engine it's trivial, you just animate the offset of the texture. Can you perhaps offset the layer, or something, in iOS?
What's the best way?
I guess you'd want to use the GPU (layer animation right?) to avoid melting the cpu.
#IBDesignable class DottedVertical: UIView {
#IBInspectable var dotColor: UIColor = UIColor.faveColor
override func draw(_ rect: CGRect) {
// say you want 8 dots, with perfect fenceposting:
let totalCount = 8 + 8 - 1
let fullHeight = bounds.size.height
let width = bounds.size.width
let itemLength = fullHeight / CGFloat(totalCount)
let beginFromTop = !lowerHalfOnly ? 0.0 : (fullHeight * 8.0 / 15.0)
let top = CGPoint(x: width/2, y: beginFromTop)
let bottom = CGPoint(x: width/2, y: fullHeight)
let path = UIBezierPath()
path.move(to: top)
path.addLine(to: bottom)
path.lineWidth = width
let dashes: [CGFloat] = [itemLength, itemLength]
path.setLineDash(dashes, count: dashes.count, phase: 0)
dotColor.setStroke()
path.stroke()
}
(Bonus - if you had a few of these on screen, they'd have to be synced of course. There'd need to be a "sync" call that starts the running animation, so you can start them all at once with a notification or other message.)
Hate to answer my own question, here's a copy and paste solution based on the Men's suggestions above!
Good one! Superb effect...
#IBDesignable class DottedVertical: UIView {
#IBInspectable var dotColor: UIColor = sfBlack6 { didSet {setup()} }
override func layoutSubviews() { setup() }
var s:CAShapeLayer? = nil
func setup() {
// say you want 8 dots, with perfect fenceposting:
- calculate exactly as in the example in the question above -
// marching ants...
if (s == nil) {
s = CAShapeLayer()
self.layer.addSublayer(s!)
}
s!.strokeColor = dotColor.cgColor
s!.fillColor = backgroundColor?.cgColor
s!.lineWidth = width
let ns = NSNumber(value: Double(itemLength))
s!.lineDashPattern = [ns, ns]
let path = CGMutablePath()
path.addLines(between: [top, bottom])
s!.path = path
let anim = CABasicAnimation(keyPath: "lineDashPhase")
anim.fromValue = 0
anim.toValue = ns + ns
anim.duration = 1.75 // seconds
anim.repeatCount = Float.greatestFiniteMagnitude
s!.add(anim, forKey: nil)
self.layer.addSublayer(s!)
}
}

Can I draw a thumbnail for a polyline?

if a I have a path for a polyline saved as string,
From google maps sdk: path.encodedPath()
Or I have a series of latlngs points,
Can I draw a thumbnail for that path ?
I don't want to draw it on mapView, I wonder if I can draw it in any other view like imageView or any thing similar.
I created swift 3 code which you can just copy and paste in playground and see the results immediately
the code is here:
import UIKit
import PlaygroundSupport
var str = "Hello, playground"
//All you need is to create a path with that points and create image or layer with that path
//To perpare for this let make some extensions with helper code
//Extension for UIBeziePath to easily create it from points
extension UIBezierPath
{
convenience init(points:[CGPoint])
{
self.init()
//connect every points by line.
//the first point is start point
for (index,aPoint) in points.enumerated()
{
if index == 0 {
self.move(to: aPoint)
}
else {
self.addLine(to: aPoint)
}
}
}
}
//to create image from path you can use this class function
extension UIImage
{
class func imageFrom(path:UIBezierPath,lineColor:UIColor,fillColor:UIColor)->UIImage
{
//create context to draw in use path bounds as context size. assume that path is inzide of rect with start corener at 0,0 coordinate
UIGraphicsBeginImageContextWithOptions(path.bounds.size, false, 0)
print("path bounds \(path.bounds) lineWidth:\(path.lineWidth)")
let context = UIGraphicsGetCurrentContext()
//set fill color
context?.setFillColor(fillColor.cgColor)
//set line coolor
context?.setStrokeColor(lineColor.cgColor)
context?.setLineWidth(path.lineWidth)
//draw a path
context?.addPath(path.cgPath)
context?.drawPath(using: .fillStroke)
//get image from context
let image = UIGraphicsGetImageFromCurrentImageContext()!
//finish context
UIGraphicsEndImageContext()
return image
}
}
//2. To create layer use this extension
extension CAShapeLayer
{
convenience init(path:UIBezierPath, lineColor:UIColor, fillColor:UIColor)
{
self.init()
self.path = path.cgPath
self.strokeColor = lineColor.cgColor
self.fillColor = fillColor.cgColor
self.lineWidth = path.lineWidth
self.opacity = 1
self.frame = path.bounds
}
}
//how to use:
//1. assume you recieved points
let points:[CGPoint] = [CGPoint(x: 0, y: 0),CGPoint(x: 150, y: 50),CGPoint(x: 75, y:140),CGPoint(x: 0, y: 80)]
//2. create path
let path = UIBezierPath(points: points)
//3. you can specify path line width
path.lineWidth = 2
//4. as a joinstyle too
path.lineJoinStyle = .round
//5. a)now you can create image from path with helper function
let image = UIImage.imageFrom(path: path, lineColor: UIColor.purple, fillColor: UIColor.red)
print(image)
//and set it to imageView
let imageView = UIImageView(image: image)
imageView.frame.origin = CGPoint(x: 200, y: 200)
imageView.backgroundColor = UIColor.green
//5. Maybe you will need to specify content mode for imageView
imageView.contentMode = .scaleAspectFit
//5 b.) Or you can create a Layer. Add add it to someone's layer layter
//if you need, you can apply transform to path - this is special way to
//adjust scale, rotation an lots of other cool stuff on layers, paths.
//Create special struct which descripbes transformation
//Identity is a special case which does not make any transformations at all
var transform = CGAffineTransform.identity
//scale it by 0.5 for x and 0.5 for y. if you need to increse scale by
//100 times, just pass 100 for x and y arguments
transform = transform.scaledBy(x: 0.5, y: 0.5)
//no apply transform to path.
path.apply(transform)
let layer = CAShapeLayer(path: path, lineColor: UIColor.blue, fillColor: UIColor.brown)
//6. let see results
let container = UIView(frame: CGRect(x: 0, y: 0, width: 400, height: 400))
container.backgroundColor = UIColor.white
//for imageView
container.addSubview(imageView)
//for CAShapeLayer
container.layer.addSublayer(layer)
//for playGround you can set this to see result there
//Do not forget to select from menu
//View -> Assistant Editor-> Show Assistance Editor
PlaygroundPage.current.liveView = container
PlaygroundPage.current.needsIndefiniteExecution = true
//Also I have to mention that the CAShapeLayer solution takes less memory which is critical for really big images
//but the UIImage is easier to use
the brown figure is layer with path scaled by 0.5, the red one is imageView
If you have a series of lat longs, you can know the maximum and minimum lat long, say they are : maxLat, maxLong, minLat, minLong. Please not that the both max values need not belong to the same coordinate. Same for both min values.
You can use this to get a rect :
let rect = CGRect(x: minLng , y: minLat, width: (maxLng - minLng), height: (maxLat - minLat))
Now, all other lat longs are points in this rectangle. You can get every coordinate's corresponding CGPoint value by
let point = CGPoint(x: maxLng - coordinate.longitude, y: maxLat - coordinate.latitude)
Using this rect and the series of CGPoints you created, you can draw a path (by starting with the first point and adding all subsequent points to it) on a view (like the image view you mention in your answer) or create a graphics context just to create a thumbnail of your path and save it as an image.
Refer the drawing and printing guide if you are unfamiliar with drawing in CGContexts.
However, if you mean to place this thumbnail equivalent of path on an image the real challenge is, how would you know the position. A simple trick would be to get map-equivalent min max coordinates of the image on which you mean to superimpose the path thumbnail and use these min max values to create the path and the context. Then you can center the thumbnail on the image.
Your project sounds interesting. Enjoy, and good luck.

CAShapeLayer isn't visible

I have a subclass of UIView connected to a XIB file of a UIViewController.
I'm using PocketSVG to convert my SVG file to CGPath like that:
override func awakeFromNib() {
let myPath = PocketSVG.pathFromSVGFileNamed("").takeUnretainedValue()
let myShapeLayer = CAShapeLayer()
myShapeLayer.path = myPath
myShapeLayer.strokeColor = UIColor.redColor().CGColor
myShapeLayer.lineWidth = 3
myShapeLayer.fillColor = UIColor.whiteColor().CGColor
self.layer.addSublayer(myShapeLayer)
}
the problem is that when I run the app I can't see anything, the layer isn't visible.
What am I doing wrong now?
Thank you!
Given what you reported for CGPathGetPathBoundingBox, you may want to transform the path so it falls within the visible portion of the window.
For example, if you want to translate and scale this so it fits the view:
let insetRect = CGRectInset(bounds, lineWidth / 2.0, lineWidth / 2.0)
let boundingRect = CGPathGetPathBoundingBox(myPath)
let scale = min(insetRect.size.height / boundingRect.size.height, insetRect.size.width / boundingRect.size.width)
var transform = CGAffineTransformScale(CGAffineTransformMakeTranslation(-boundingRect.origin.x * scale + insetRect.origin.x, -boundingRect.origin.y * scale + insetRect.origin.y), scale, scale)
let transformedPath = CGPathCreateMutableCopyByTransformingPath(myPath, &transform)
There are lots of permutations on this idea, but hopefully it illustrates the concept.

What is the right way of creating circle animation?

I just saw this image and it's interesting to me, how to create such type of animation in Swift:
So, I have many gray teeth in circle and when I set the angle, for example 45degree it will fill these gray teeth into blue within 0..45 degree.
You can just explain me the right way of doing it or you can show different snippets(it would be great). And later I will search or read about it.
Thanks in advance!
If you only need the individual 'teeth' to change color, instead of using the teeth as masks for a solid fill, you can use Core Graphics instead of Core Animation (although Core Animation is generally preferred). So in order to do this, we should be doing the following:
Subclass UIView to insert our drawing code
Create an array of path objects, wrapped in UIBezierPath
Setup a timer to update a progress value and setNeedsDisplay
In drawRect:, draw the paths and assign a fill to each depending on the progress
First of all, lets define the variables we're going to be working with in this UIView subclass.
class TeethLoaderView : UIView {
let numberOfTeeth = UInt(60) // Number of teeth to render
let teethSize = CGSize(width:8, height:45) // The size of each individual tooth
let animationDuration = NSTimeInterval(5.0) // The duration of the animation
let highlightColor = UIColor(red: 29.0/255.0, green: 175.0/255.0, blue: 255.0/255.0, alpha: 1) // The color of a tooth when it's 'highlighted'
let inactiveColor = UIColor(red: 233.0/255.0, green: 235.0/255.0, blue: 236.0/255.0, alpha: 1) // The color of a tooth when it isn't 'hightlighted'
var progress = NSTimeInterval(0.0) // The progress of the loader
var paths = [UIBezierPath]() // The array containing the UIBezier paths
var displayLink = CADisplayLink() // The display link to update the progress
var teethHighlighted = UInt(0) // Number of teeth highlighted
...
Now let's add a function to create our paths.
func getPaths(size:CGSize, teethCount:UInt, teethSize:CGSize, radius:CGFloat) -> [UIBezierPath] {
let halfHeight = size.height*0.5;
let halfWidth = size.width*0.5;
let deltaAngle = CGFloat(2*M_PI)/CGFloat(teethCount); // The change in angle between paths
// Create the template path of a single shape.
let p = CGPathCreateWithRect(CGRectMake(-teethSize.width*0.5, radius, teethSize.width, teethSize.height), nil);
var pathArray = [UIBezierPath]()
for i in 0..<teethCount { // Copy, translate and rotate shapes around
let translate = CGAffineTransformMakeTranslation(halfWidth, halfHeight);
var rotate = CGAffineTransformRotate(translate, deltaAngle*CGFloat(i))
let pathCopy = CGPathCreateCopyByTransformingPath(p, &rotate)!
pathArray.append(UIBezierPath(CGPath: pathCopy)) // Populate the array
}
return pathArray
}
This is fairly simple. We just create a path for a single 'tooth' and then copy this path for how many teeth we need, translating and rotating the path for each one.
Next we want to setup our view. I'm going to a CADisplayLink for the timer so that the animation performs at the same speed on all devices.
override init(frame: CGRect) {
super.init(frame: frame)
commonSetup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonSetup()
}
private func commonSetup() {
self.backgroundColor = UIColor.whiteColor()
paths = getPaths(frame.size, teethCount: numberOfTeeth, teethSize: teethSize, radius: ((frame.width*0.5)-teethSize.height))
displayLink = CADisplayLink(target: self, selector: #selector(displayLinkDidFire));
displayLink.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes)
}
Here we just set the background color, as well as setup our timer and initialise the paths we're going to be using. Next we want to setup a function to change the progress of the view when the CADisplayLink fires.
func displayLinkDidFire() {
progress += displayLink.duration/animationDuration
if (progress > 1) {
progress -= 1
}
let t = teethHighlighted
teethHighlighted = UInt(round(progress*NSTimeInterval(numberOfTeeth))) // Calculate the number of teeth to highlight
if (t != teethHighlighted) { // Only call setNeedsDisplay if the teethHighlighted changed
setNeedsDisplay()
}
}
Nothing complicated here, we just update the progress and teethHighlighted and call setNeedsDisplay() to redraw the view, if teethHighlighted changed.
Finally, we want to draw the view.
override func drawRect(rect: CGRect) {
let ctx = UIGraphicsGetCurrentContext()
CGContextScaleCTM(ctx, -1, -1) // Flip the context to the correct orientation
CGContextTranslateCTM(ctx, -rect.size.width, -rect.size.height)
for (index, path) in paths.enumerate() { // Draw each 'tooth'
CGContextAddPath(ctx, path.CGPath);
let fillColor = (UInt(index) <= teethHighlighted) ? highlightColor:inactiveColor;
CGContextSetFillColorWithColor(ctx, fillColor.CGColor)
CGContextFillPath(ctx)
}
}
If you wanted to go down the Core Animation path, I adapted this code into a Core Animation layer
Final Result
Full project: https://github.com/hamishknight/Circle-Loader
Well, in the spirit of "go big or go home" (and because I'm actually having some fun doing this), I created a Core Animation version of my Core Graphics answer. It's quite a bit less code and animates smoother, so I'd actually prefer to use this.
First off, let's subclass a UIView again (this isn't strictly necessary, but it's nice to contain everything in a single view) and define our variables:
class TeethLoaderViewCA : UIView {
let numberOfTeeth = UInt(60) // Number of teetch to render
let teethSize = CGSize(width:8, height:45) // The size of each individual tooth
let animationDuration = NSTimeInterval(5.0) // The duration of the animation
let highlightColor = UIColor(red: 29.0/255.0, green: 175.0/255.0, blue: 255.0/255.0, alpha: 1) // The color of a tooth when it's 'highlighted'
let inactiveColor = UIColor(red: 233.0/255.0, green: 235.0/255.0, blue: 236.0/255.0, alpha: 1) // The color of a tooth when it isn't 'hightlighted'
let shapeLayer = CAShapeLayer() // The teeth shape layer
let drawLayer = CAShapeLayer() // The arc fill layer
let anim = CABasicAnimation(keyPath: "strokeEnd") // The stroke animation
...
This is mostly the same as the Core Graphics version, but with a couple of Core Animation objects and without the timing logic. Next, we can pretty much copy the getPaths function we created in the other version, except with a few tweaks.
func getPathMask(size:CGSize, teethCount:UInt, teethSize:CGSize, radius:CGFloat) -> CGPathRef? {
let halfHeight = size.height*0.5
let halfWidth = size.width*0.5
let deltaAngle = CGFloat(2*M_PI)/CGFloat(teethCount); // The change in angle between paths
// Create the template path of a single shape.
let p = CGPathCreateWithRect(CGRectMake(-teethSize.width*0.5, radius, teethSize.width, teethSize.height), nil)
let returnPath = CGPathCreateMutable()
for i in 0..<teethCount { // Copy, translate and rotate shapes around
let translate = CGAffineTransformMakeTranslation(halfWidth, halfHeight)
var rotate = CGAffineTransformRotate(translate, deltaAngle*CGFloat(i))
CGPathAddPath(returnPath, &rotate, p)
}
return CGPathCreateCopy(returnPath)
}
This time, all the paths are grouped into one big path and the function returns that path.
Finally, we just have to create our layer objects & setup the animation.
private func commonSetup() {
// set your background color
self.backgroundColor = UIColor.whiteColor()
// Get the group of paths we created.
shapeLayer.path = getPathMask(frame.size, teethCount: numberOfTeeth, teethSize: teethSize, radius: ((frame.width*0.5)-teethSize.height))
let halfWidth = frame.size.width*0.5
let halfHeight = frame.size.height*0.5
let halfDeltaAngle = CGFloat(M_PI/Double(numberOfTeeth))
// Creates an arc path, with a given offset to allow it to be presented nicely
drawLayer.path = UIBezierPath(arcCenter: CGPointMake(halfWidth, halfHeight), radius: halfWidth, startAngle: CGFloat(-M_PI_2)-halfDeltaAngle, endAngle: CGFloat(M_PI*1.5)+halfDeltaAngle, clockwise: true).CGPath
drawLayer.frame = frame
drawLayer.fillColor = inactiveColor.CGColor
drawLayer.strokeColor = highlightColor.CGColor
drawLayer.strokeEnd = 0
drawLayer.lineWidth = halfWidth
drawLayer.mask = shapeLayer
layer.addSublayer(drawLayer)
// Optional, but looks nice
anim.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
}
All we're doing here is assigning the group of paths to a CAShapeLayer, which we will use as a mask over the drawLayer, which we will be animating around the view (using a stroke on an arch path).
Final Result
Full project: https://github.com/hamishknight/Circle-Loader

How to Wrap Text Around a Circle in Sprite Kit / Swift

I am making a game with SpriteKit / Swift and I want to have an effect on the menu scene where I bend a string around a circle. The following picture is almost exactly what I am looking to accomplish. http://www.heathrowe.com/tuts/typeonaapathimages/4.gif
The following code wraps the characters in a string around the top half of a circle by creating a label node for each character in the string, setting the position of the label to the appropriate location on the circle, and then rotating each label node so that it is tangent to the circle at that position.
class GameScene:SKScene {
override func didMove(to view:SKView) {
let radius = CGFloat(50.0)
let circleCenter = CGPoint.zero
let string = "Your Text Here"
let count = string.lengthOfBytes(using: String.Encoding.utf8)
let angleIncr = CGFloat.pi/(CGFloat(count)-1)
var angle = CGFloat.pi
// Loop over the characters in the string
for (_, character) in string.characters.enumerated() {
// Calculate the position of each character
let x = cos(angle) * radius + circleCenter.x
let y = sin(angle) * radius + circleCenter.y
let label = SKLabelNode(fontNamed: "Arial")
label.text = "\(character)"
label.position = CGPoint(x: x, y: y)
// Determine how much to rotate each character
label.zRotation = angle - CGFloat.pi / 2
label.fontSize = 30
addChild(label)
angle -= angleIncr
}
}
}
Binary tree is a non linear data structure. It should and must have atleast two children to a single parent node.

Resources