Plot nodes along a UIBezierPath in spritekit - ios

i'm currently developing a game in spritekit which has a game level map. I'm using an UIBezierPath for the pathway i want the level nodes to follow, the only problem I have is trying to plot them along the path and was wondering how I go about adding them to the scene so they get added to the path way and each one is individually spaced 50 apart from the last one that was plotted before it. currently I have this:
My Code
let path = UIBezierPath()
path.move(to: CGPoint(x: -200, y: 0))
path.addCurve(to: CGPoint(x: 0, y: 0), controlPoint1: CGPoint(x: 0, y: 0), controlPoint2: CGPoint(x: -200, y: 0))
path.addCurve(to: CGPoint(x: 140, y: 0), controlPoint1: CGPoint(x: 60, y: 180), controlPoint2: CGPoint(x: 140, y: 10))
path.addCurve(to: CGPoint(x: 280, y: 0), controlPoint1: CGPoint(x: 220, y: -180), controlPoint2: CGPoint(x: 280, y: 0))
path.addCurve(to: CGPoint(x: 440, y: 0), controlPoint1: CGPoint(x: 400, y: -300), controlPoint2: CGPoint(x: 440, y: 0))
let shapeNode = SKShapeNode(path: path.cgPath)
shapeNode.strokeColor = UIColor.white
addChild(shapeNode)
for i in 1...5 {
let level1 = mapLevelTiles()
level1.createAndDisplay(Data: lev3)
level1.position = CGPoint(x: 0 + level1.levelImageNode.size.width * 1.5 * CGFloat(i), y: path.cgPath.currentPoint) //path.cgPath.currentPoint
level1.zPosition = 10
addChild(level1)
}
The code above doesn't achieve what I want because it only allows me to get the end point of the last line in the path. How do I plot the nodes along the path that I have drawn?

You can use the timingFunction method of SKAction.follow(path:) to set the spacing of your sprites along the bezier path. Typically, you use the timingFunction method to customize the timing of an action, for example, to the ease-in or ease-out. In this case, you can use it to set the starting point of a sprite along a path.
For example, if you want to start the sprite at the midpoint of your bezier path, you can offset the time parameter of the timingFunction by 0.5, where the time starts at 0 and ends at 1.
let move = SKAction.follow(path.cgPath, speed: 100)
move.timingFunction = {
time in
return min(time + 0.5, 1)
}
If you run the move action on your sprite, it will start at the midpoint of your path and end at the path's end point.
Here's an example to set the spacing for a set of sprites along a bezier path
// Show the path
let shapeNode = SKShapeNode(path: path.cgPath)
shapeNode.strokeColor = UIColor.white
addChild(shapeNode)
// Add 5 sprites to the scene at various points along the path
for i in 1...5 {
let move = SKAction.follow(path.cgPath, speed: 100)
move.timingFunction = {
time in
return min(time + 0.1 * Float(i), 1)
}
let sprite = SKSpriteNode(color: .blue, size: CGSize(width: 20, height: 20))
addChild(sprite)
sprite.run(move)
}

You can move your sprites one by one using following code.
let path = UIBezierPath()
path.move(to: CGPoint(x: -200, y: 0))
path.addCurve(to: CGPoint(x: 0, y: 0), controlPoint1: CGPoint(x: 0, y: 0), controlPoint2: CGPoint(x: -200, y: 0))
path.addCurve(to: CGPoint(x: 140, y: 0), controlPoint1: CGPoint(x: 60, y: 180), controlPoint2: CGPoint(x: 140, y: 10))
path.addCurve(to: CGPoint(x: 280, y: 0), controlPoint1: CGPoint(x: 220, y: -180), controlPoint2: CGPoint(x: 280, y: 0))
path.addCurve(to: CGPoint(x: 440, y: 0), controlPoint1: CGPoint(x: 400, y: -300), controlPoint2: CGPoint(x: 440, y: 0))
let shapeNode = SKShapeNode(path: path.cgPath)
shapeNode.strokeColor = UIColor.white
addChild(shapeNode)
for i in 1...5 {
let level1 = SKShapeNode(circleOfRadius: 30.0)
level1.zPosition = 10
addChild(level1)
let followPath = SKAction.follow(path.cgPath, asOffset: true, orientToPath: true, duration: 5.0)
level1.run(SKAction.sequence([SKAction.hide(),
SKAction.wait(forDuration: 2.0 * Double(i)),
SKAction.unhide(),
followPath,
SKAction.removeFromParent()]))
}

Related

The effect displayed by SKShapeNode is different from that of CAShapelayer

I am going to use SKShapeNode to display a Bezier curve graph, but the displayed effect is very strange, and it is not the same as the one displayed by CAShapeLayer. I don't know what is wrong. Does anyone know the problem?
Here is a screenshot of the effect I showed in two different ways.
The effect displayed by CAShapeLayer looks normal and is as expected
the effect displayed by SKShapeNode misses some lines
here is my main code:
let bezierPath = UIBezierPath()
bezierPath.move(to: CGPoint(x: 54.5, y: 244.5))
bezierPath.addCurve(to: CGPoint(x: 54.5, y: 164.5), controlPoint1: CGPoint(x: 53.5, y: 243.5), controlPoint2: CGPoint(x: 29.5, y: 201.5))
bezierPath.addCurve(to: CGPoint(x: 54.5, y: 70.5), controlPoint1: CGPoint(x: 79.5, y: 127.5), controlPoint2: CGPoint(x: 54.5, y: 70.5))
bezierPath.addLine(to: CGPoint(x: 227.5, y: 70.5))
bezierPath.addCurve(to: CGPoint(x: 227.5, y: 164.5), controlPoint1: CGPoint(x: 227.5, y: 70.5), controlPoint2: CGPoint(x: 254.5, y: 127.5))
bezierPath.addCurve(to: CGPoint(x: 227.5, y: 244.5), controlPoint1: CGPoint(x: 200.5, y: 201.5), controlPoint2: CGPoint(x: 227.5, y: 244.5))
bezierPath.addCurve(to: CGPoint(x: 54.5, y: 244.5), controlPoint1: CGPoint(x: 227.5, y: 244.5), controlPoint2: CGPoint(x: 55.5, y: 245.5))
bezierPath.close()
let scene = SKScene.init(size: CGSize.init(width: 300, height: 300))
scene.backgroundColor = UIColor.white
self.contentView.presentScene(scene)
let node = SKShapeNode.init()
node.path = bezierPath.cgPath
node.lineJoin = .miter
node.lineCap = .round
node.miterLimit = 10
node.strokeColor = SKColor.blue
node.fillColor = SKColor.red
node.lineWidth = 10
node.isAntialiased = true
scene.addChild(node)
this code should work for you -- adding the path elements in a clockwise direction
let N:CGFloat = 300 //box size
let C:CGFloat = 20 //control offset
let bezierPath = UIBezierPath()
bezierPath.move(to: CGPoint(x: 0, y: 0))
bezierPath.addCurve(to: CGPoint(x:0, y:N*0.5),
controlPoint1: CGPoint(x:C, y:N*0.15),
controlPoint2: CGPoint(x:C, y:N*0.35))
bezierPath.addCurve(to: CGPoint(x:0, y:N),
controlPoint1: CGPoint(x:-C, y:N*0.65),
controlPoint2: CGPoint(x:-C, y:N*0.85))
bezierPath.addLine(to: CGPoint(x:N, y:N))
bezierPath.addCurve(to: CGPoint(x:N, y:N*0.5),
controlPoint1: CGPoint(x:N-C, y:N*0.85),
controlPoint2: CGPoint(x:N-C, y:N*0.65))
bezierPath.addCurve(to: CGPoint(x:N, y:0),
controlPoint1: CGPoint(x:N+C, y:N*0.35),
controlPoint2: CGPoint(x:N+C, y:N*0.15))
bezierPath.close()

Waved Curved Tabbar Design in swift 4.2 İOS

Wave curved (upside) tabbar behind the center raised button. İ have created a customised class for tabbar by using reference of "https://medium.com/#philipp307/draw-a-custom-ios-tabbar-shape-27d298a7f4fa" But actually İ need to create a Wave Curved in the center of tab bar. İ have tried to play around with Bézier curve but not getting an exact behind the central button. I don't need a dip of it. İ need it behind (upside wave) my custom code and images attached.
func createPath() -> CGPath {
let height: CGFloat = 37.0
let path = UIBezierPath()
let centerWidth = self.frame.width / 2
path.move(to: CGPoint(x: 0, y: 0)) // start top left
path.addLine(to: CGPoint(x: (centerWidth - height * 2), y: 0)) // the beginning of the trough
// first curve down
path.addCurve(to: CGPoint(x: centerWidth, y: height),
controlPoint1: CGPoint(x: (centerWidth - 30), y: 0), controlPoint2: CGPoint(x: centerWidth - 35, y: height))
// second curve up
path.addCurve(to: CGPoint(x: (centerWidth + height * 2), y: 0),
controlPoint1: CGPoint(x: centerWidth + 35, y: height), controlPoint2: CGPoint(x: (centerWidth + 30), y: 0))
// complete the rect
path.addLine(to: CGPoint(x: self.frame.width, y: 0))
path.addLine(to: CGPoint(x: self.frame.width, y: self.frame.height))
path.addLine(to: CGPoint(x: 0, y: self.frame.height))
path.close()
return path.cgPath
}
tabbar design

CAShapeLayer draws different line width for one path, Swift

i have a problem while drawing curve:
strokePath = UIBezierPath()
strokePath.move(to: CGPoint(x:curveWidth / 2.0 - headerWidth, y: headerHeight))
strokePath.addLine(to: CGPoint(x: 0, y: headerHeight))
strokePath.addCurve(to: CGPoint(x: curveRadius, y: headerHeight - curveRadius),
controlPoint1: CGPoint(x: degree, y: headerHeight),
controlPoint2: CGPoint(x: curveRadius, y: headerHeight - curveRadius))
strokePath.addLine(to: CGPoint(x: curveWidth - curveRadius, y: curveRadius))
strokePath.addCurve(to: CGPoint(x: curveWidth, y: 0),
controlPoint1: CGPoint(x: curveWidth - curveRadius + curveRadius * 0.25, y: 0),
controlPoint2: CGPoint(x: curveWidth, y: 0))
strokePath.addLine(to: CGPoint(x: self.frame.size.width, y: 0.0))
strokeLayer.path = strokePath.cgPath
strokeLayer.strokeColor = uiConfig.color.misc.tabGrayBorder.cgColor
strokeLayer.fillColor = UIColor.clear.cgColor
strokeLayer.lineWidth = 0.5
self.layer.addSublayer(strokeLayer)
and i have unexpectable result:
curve is bold than the line, have somebody idea why it happened and how it fixing?
sorry, its my mistake after review many lines of code i find active "Clip to bounds" property of one of my parent view

Mask UIView (UICollectionView) correctly

Ok, I need to mask a horizontal UICollectionViewso it looks like a ribbon. My web dev counterpart got it done with an SVG that he used as a masking layer.
Here is my current situation:
And here is what I need (Photo from our web app):
Small detail in image spacing aside, I have an SVG like this:
Which I can successfully convert into UIBezierPath code (Paintcode):
let pathPath = UIBezierPath()
pathPath.move(to: CGPoint(x: 0.05, y: 0))
pathPath.addCurve(to: CGPoint(x: 162.02, y: 3.8), controlPoint1: CGPoint(x: 0.05, y: 2.1), controlPoint2: CGPoint(x: 72.57, y: 3.8))
pathPath.addCurve(to: CGPoint(x: 324, y: 0), controlPoint1: CGPoint(x: 251.48, y: 3.8), controlPoint2: CGPoint(x: 324, y: 2.1))
pathPath.addLine(to: CGPoint(x: 324, y: 150.2))
pathPath.addCurve(to: CGPoint(x: 162.02, y: 154), controlPoint1: CGPoint(x: 324, y: 152.3), controlPoint2: CGPoint(x: 251.48, y: 154))
pathPath.addCurve(to: CGPoint(x: 0.05, y: 150.2), controlPoint1: CGPoint(x: 72.57, y: 154), controlPoint2: CGPoint(x: 0.05, y: 152.3))
pathPath.addCurve(to: CGPoint(x: 0, y: 0.17), controlPoint1: CGPoint(x: 0.05, y: 148.1), controlPoint2: CGPoint(x: 0, y: 0.17))
pathPath.usesEvenOddFillRule = true
UIColor.lightGray.setFill()
pathPath.fill()
Now what do I do?
You should use the mask property of your UICollectionView by setting it to a view whose alpha channel indicates what part of the UICollectionView you want to mask out. In outline it will probably be something like:
// If you don't have a custom subclass of UICollectionView... you can handle the resize in the
// UICollectionViewController or whatever view contoller is handling your view.
//
// If you do have a custom subclass of UICollectionView... you could do something similar
// in layoutSubviews.
class MyViewController : UICollectionViewController {
// recreate the mask after the view lays out it's subviews
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if let maskImage = createMaskingImage(size: self.view.bounds.size) {
let maskView = UIView(frame: self.view.bounds)
maskView.layer.contents = maskImage
self.view.mask = maskView
}
}
}
// create a masking image for a view of the given size
func createMaskingImage(size: CGSize) -> UIImage? {
let drawingBounds = CGRect(origin: CGPoint.zero, size: size)
UIGraphicsBeginImageContext(size)
// We're going to jump down to the level of cgContext for scaling
// You might be able to do this from the level of UIGraphics, but I don't know how so...
// Implicitly unwrapped optional, but if we can't get a CGContext we're in trouble
let cgContext = UIGraphicsGetCurrentContext()!
let maskingPath = createMaskingPath()
let pathBounds = maskingPath.bounds;
cgContext.saveGState()
// Clearing the image may not strictly be necessary
cgContext.clear(drawingBounds)
// Scale the context so that when we draw the path it fits in the drawing bounds
// Could just use "size" here instead of drawingBounds.size, but I think this makes it a
// little more explicit that we're matching up two rects
cgContext.scaleBy(x: drawingBounds.size.width / pathBounds.size.width,
y: drawingBounds.size.height / pathBounds.size.height)
cgContext.setFillColor(UIColor.lightGray.cgColor)
cgContext.addPath(maskingPath.cgPath)
cgContext.fillPath(using: .evenOdd)
cgContext.restoreGState()
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
func createMaskingPath() -> UIBezierPath {
let path = UIBezierPath()
path.move(to: CGPoint(x: 0.05, y: 0))
path.addCurve(to: CGPoint(x: 162.02, y: 3.8), controlPoint1: CGPoint(x: 0.05, y: 2.1), controlPoint2: CGPoint(x: 72.57, y: 3.8))
path.addCurve(to: CGPoint(x: 324, y: 0), controlPoint1: CGPoint(x: 251.48, y: 3.8), controlPoint2: CGPoint(x: 324, y: 2.1))
path.addLine(to: CGPoint(x: 324, y: 150.2))
path.addCurve(to: CGPoint(x: 162.02, y: 154), controlPoint1: CGPoint(x: 324, y: 152.3), controlPoint2: CGPoint(x: 251.48, y: 154))
path.addCurve(to: CGPoint(x: 0.05, y: 150.2), controlPoint1: CGPoint(x: 72.57, y: 154), controlPoint2: CGPoint(x: 0.05, y: 152.3))
path.addCurve(to: CGPoint(x: 0, y: 0.17), controlPoint1: CGPoint(x: 0.05, y: 148.1), controlPoint2: CGPoint(x: 0, y: 0.17))
return path
}
/*
This is a UICollectionView, which clips normally on the left and right,
but allows some extra space horizontally.
A typical example is you need to clip the scrolling items but you
still need to allow shadows.
*/
import Foundation
import UIKit
class CustomClipCollectionView: UICollectionView {
private lazy var extraSpaceOnBaseButStillClipSidesNormally: CALayer = {
let l = CALayer()
l.backgroundColor = UIColor.black.cgColor
return l
}()
override func layoutSubviews() {
extraSpaceOnBaseButStillClipSidesNormally.frame = bounds.insetBy(
dx: 0, dy: -10)
layer.mask = extraSpaceOnBaseButStillClipSidesNormally
super.layoutSubviews()
}
}

How can I create arc shape ios swift

I have an application with some buttons. I want to create a arc shape button like the following image
How can I do it? I use the following code to achieve this.
button.layer.cornerRadius = button.bounds.size.height
and
button.layer.cornerRadius = 0.5*button.bounds.size.height
I also tried this with the using of width
button.layer.cornerRadius = button.bounds.size.width
and
button.layer.cornerRadius = 0.5*button.bounds.size.width
How can I do it? Please someone help me to solve this.
To create arc shape you need to use UIBezierPath
please read apple's UIBezierPath_class Document.
They have mention method for create arc shape.
Creates and returns a new UIBezierPath object initialized with an arc of a circle.
For constructing the path
Appends an arc to the receiver’s path.
You can also Refer Core Graphics Tutorial Part 1 With Example and search with arc keyword on browser you will redirect to arc shape on the page.
Another image posted by #Jack in above comments and asked help, here is the image:
I couldn't answer him as comment, so pasting my playground here instead.
let shapeView = UIView(frame: CGRect(x: 0, y: 0, width: 360, height: 60))
let shapeSize = shapeView.frame.size
shapeView.backgroundColor = UIColor.white
let path = CGMutablePath()
path.move(to: CGPoint.zero)
let curveWidthOneFourth = shapeSize.width / 4
let curveHeight = shapeSize.height * 0.2
path.addCurve(to: CGPoint(x: curveWidthOneFourth * 2, y: curveHeight), control1: .zero, control2: CGPoint(x: curveWidthOneFourth, y: curveHeight))
path.addCurve(to: CGPoint(x: shapeSize.width, y: 0), control1: CGPoint(x: curveWidthOneFourth * 3, y: curveHeight), control2: CGPoint(x: shapeSize.width, y: 0))
path.addLine(to: CGPoint(x: shapeSize.width, y: shapeSize.height))
path.addLine(to: CGPoint(x: 0, y: shapeSize.height))
path.addLine(to: CGPoint.zero)
And add our path to a view's layer:
let layer = CAShapeLayer()
layer.path = path
layer.fillColor = UIColor.red.cgColor
shapeView.layer.addSublayer(layer)
Encountered a similar task today, I used CGMutableShape instead of UIBezierPath because I was required to animation the shape.
Anyways, to understand the shape I divided the shape into 6 parts (lines & curves):
Base UIView to draw our shape in
let shapeView = UIView(frame: CGRect(x: 16, y: 16, width: 100, height: 44))
shapeView.backgroundColor = UIColor.lightGray
self.view.addSubview(shapeView)
Actual shape and its path
let path = CGMutablePath()
path.move(to: CGPoint(x: 10, y: 0)) // Move path to beginning of part-1
// Draw above divided parts
path.addLine(to: CGPoint(x: 90, y: 0)) // Part-1
path.addCurve(to: CGPoint(x: 95, y: 22), control1: CGPoint(x: 93, y: 6.6), control2: CGPoint(x: 95, y: 14)) // Part-2
path.addCurve(to: CGPoint(x: 90, y: 44), control1: CGPoint(x: 95, y: 30), control2: CGPoint(x: 93, y: 37)) // Part-3
path.addLine(to: CGPoint(x: 10, y: 44)) // Part-4
path.addCurve(to: CGPoint(x: 5, y: 22), control1: CGPoint(x: 7, y: 37), control2: CGPoint(x: 5, y: 30)) // Part-5
path.addCurve(to: CGPoint(x: 10, y: 0), control1: CGPoint(x: 5, y: 14), control2: CGPoint(x: 6, y: 6.6)) // Part-6
let arcLayer = CAShapeLayer()
arcLayer.path = path
arcLayer.fillColor = UIColor.gray.cgColor
shapeView.layer.insertSublayer(arcLayer, at: 0)

Resources