Compute points from Bezier Path - swift

Let's say I have a random bezier path, like this:
let bezierPath = UIBezierPath()
bezierPath.move(to: CGPoint(x: 3, y: 0.84))
bezierPath.addCurve(to: CGPoint(x: 11, y: 8.84), controlPoint1: CGPoint(x: 3, y: 1.84), controlPoint2: CGPoint(x: 9, y: 4.59))
// [...]
bezierPath.addCurve(to: CGPoint(x: 3, y: 0.84), controlPoint1: CGPoint(x: 7, y: 4.84), controlPoint2: CGPoint(x: 3, y: -0.16))
bezierPath.close()
I would like to create a function that compute a CGPoint for a given percentage, where 0% is the first point and 100% the last point of the bezierPath:
extension UIBezierPath {
func getPointFor(percentage: Float) -> CGPoint {
//Computation logic
return result
}
}
I have found this post but the solution doesn't allow me to get all points (position at 15.5% of the path for example).
Is there a way to do this?

I have found a solution written in objective-c. You can find the source code here.
I manage to use it by using a bridging header :
#ifndef bridging_header_h
#define bridging_header_h
#import "UIBezierPath+Length.h"
#endif /* bridge_header_h */
And you can use the two functions like this:
print("length=\(bezierPath.length())")
for i in 0...100 {
let percent:CGFloat = CGFloat(i) / 100.0
print("point at [\(percent)]=\(bezierPath.point(atPercentOfLength: percent))")
}
output:
length=143.316117804497
point at [0.0]=(3.0, 0.839999973773956)
point at [0.01]=(3.26246070861816, 1.29733419418335)
point at [0.02]=(3.97137236595154, 1.91627132892609)
point at [0.03]=(5.00902938842773, 2.69386911392212)
[...]
point at [0.99]=(3.27210903167725, 0.765813827514648)
point at [1.0]=(3.0, 0.839999973773956)

Related

ARKit Drawing 3D paper plane with rounded corner

I'm trying to draw 3D paper plane with rounded corner in ARKit, but I can't do that.
I did that with bazier path:
// create bezeir path
let path = UIBezierPath()
// A bezier path
path.move(to: CGPoint(x: 0, y: 0.025))
path.addLine(to: CGPoint(x: 0.02, y: -0.005))
path.addLine(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: -0.02, y: -0.005))
path.close()
// create arrow shape
let arrowShape = SCNShape(path: path, extrusionDepth: 0.001)
arrowShape.chamferRadius = 50
// create new node
arrownode = SCNNode(geometry: arrowShape);
// set arrow color
arrownode!.geometry?.firstMaterial?.diffuse.contents = UIColor.yellow
The result :
I need like this exactly but with rounded corner.
An instance property chamferRadius creates chamfer for extruded zDepth, not for XY path.
let arrowShape = SCNShape(path: path, extrusionDepth: 0.005)
arrowShape.chamferRadius = 20
If you want to create a rounded corners use the following method for path:
path.addQuadCurve(to: CGPoint, controlPoint: CGPoint)
or
path.addCurve(to: CGPoint, controlPoint1: CGPoint, controlPoint2: CGPoint)

How do I cast two shadows from an UIImageView with a SF Symbol as the image?

I want to cast a shadow to the lower left AND the lower right of my Image. But I'm struggling to get the second shadow cast (layer2)...
I've created a subclass of UIImage view and use that in IB:
import UIKit
class ShadowImageView: UIImageView {
var layer2 = CALayer()
override func layoutSubviews() {
super.layoutSubviews()
self.layer2 = CALayer(layer: self.layer)
self.clipsToBounds = false
self.layer.shadowColor = UIColor.gray.cgColor
self.layer.shadowOffset = CGSize(width: -16, height: 16)
self.layer.shadowOpacity = 0.33
self.layer.shadowRadius = 3
self.layer.masksToBounds = false
self.layer2.shadowColor = UIColor.gray.cgColor
self.layer2.shadowOffset = CGSize(width: 16, height: 16)
self.layer2.shadowOpacity = 0.33
self.layer2.shadowRadius = 3
self.layer2.masksToBounds = false
self.layer.insertSublayer(self.layer2, at: 0)
print("\(self.layer)\n")
if let sublayers = self.layer.sublayers {
for layer in sublayers {
print("\(layer)\n")
}
}
}
}
Am I going in the right direction to copy the layer and apply a new shadow to it or should I look in a different direction?
Fixed the shadow problem. I was able to generate double shadows by adding 3 layers on my UIView. The furthest behind (the first sublayer of the view's layer) is the darker shadow, the one on top of that is the lighter shadow and on top of that I have one with the normal color.
I'm 'drawing' the images on the layers by applying a shadow path & color.
The image I'm drawing is coming from a method like listed below, but it's not being generated from a SFSymbols. I would like to generate it during runtime so I end up with a simple method of using symbols to place Neumorphic elements in our apps. The manual generation of these BezierPaths isn't really pleasant.
static var shieldFill: UIBezierPath = {
let bezierPath = UIBezierPath()
bezierPath.move(to: CGPoint(x: 58.94, y: 115.53))
bezierPath.addCurve(to: CGPoint(x: 62.84, y: 114.4), controlPoint1: CGPoint(x: 60.06, y: 115.53), controlPoint2: CGPoint(x: 61.57, y: 115.04))
bezierPath.addCurve(to: CGPoint(x: 101.76, y: 74.46), controlPoint1: CGPoint(x: 91.5, y: 100.49), controlPoint2: CGPoint(x: 101.76, y: 91.89))
bezierPath.addLine(to: CGPoint(x: 101.76, y: 39.94))
bezierPath.addCurve(to: CGPoint(x: 92.04, y: 25.05), controlPoint1: CGPoint(x: 101.76, y: 31.84), controlPoint2: CGPoint(x: 99.41, y: 28.12))
bezierPath.addCurve(to: CGPoint(x: 67.63, y: 16.85), controlPoint1: CGPoint(x: 87.89, y: 23.29), controlPoint2: CGPoint(x: 71.29, y: 17.97))
bezierPath.addCurve(to: CGPoint(x: 58.94, y: 15.53), controlPoint1: CGPoint(x: 64.94, y: 16.11), controlPoint2: CGPoint(x: 61.52, y: 15.53))
bezierPath.addCurve(to: CGPoint(x: 50.24, y: 16.85), controlPoint1: CGPoint(x: 56.35, y: 15.53), controlPoint2: CGPoint(x: 52.93, y: 16.11))
bezierPath.addCurve(to: CGPoint(x: 25.83, y: 25.05), controlPoint1: CGPoint(x: 46.53, y: 17.97), controlPoint2: CGPoint(x: 29.93, y: 23.29))
bezierPath.addCurve(to: CGPoint(x: 16.11, y: 39.94), controlPoint1: CGPoint(x: 18.46, y: 28.12), controlPoint2: CGPoint(x: 16.11, y: 31.84))
bezierPath.addLine(to: CGPoint(x: 16.11, y: 74.46))
bezierPath.addCurve(to: CGPoint(x: 55.03, y: 114.4), controlPoint1: CGPoint(x: 16.11, y: 91.89), controlPoint2: CGPoint(x: 26.46, y: 100.29))
bezierPath.addCurve(to: CGPoint(x: 58.94, y: 115.53), controlPoint1: CGPoint(x: 56.3, y: 115.04), controlPoint2: CGPoint(x: 57.76, y: 115.53))
bezierPath.close()
return bezierPath
}()
I was able to get this very easily:
That seems to be the sort of thing you're after.
The double shadow stems from the fact that in a graphics context, shadow drawing is a state applied to any future drawing. So if you set the context's shadow to be down-and-to-the-left and draw the symbol image, and then set the context's shadow to be down-and-to-the-right and draw the symbol image, you get the double shadow.
It is true that the symbol image itself is drawn twice but that does not necessarily change the way it appears; if I hadn't told you I'd drawn it twice, I don't think you'd have known.

SKShapeNode Strokes show gaps when drawn with CGPath

I am trying to draw a shape using CGPath and SKShapeNode. However, whatever the lineJoin or lineCap combinations I try to use, the lines have gaps.
import PlaygroundSupport
import SpriteKit
class GameScene: SKScene {
override func didMove(to view: SKView) {
super.didMove(to: view)
let path = CGMutablePath()
path.move(to: NSPoint(x: -66.585, y: 1.125))
path.addCurve(to: NSPoint(x: -60.585, y: -41.765), control1: NSPoint(x: -66.585, y: 1.125), control2: NSPoint(x: -65.835, y: -32.735))
path.addCurve(to: NSPoint(x: 66.585, y: -20.3150000000001), control1: NSPoint(x: -60.585, y: -41.765), control2: NSPoint(x: -3.39500000000001, y: -1.88499999999999))
path.addCurve(to: NSPoint(x: 56.545, y: 18.235), control1: NSPoint(x: 66.125, y: -7.09500000000003), control2: NSPoint(x: 62.695, y: 6.16499999999996))
path.addCurve(to: NSPoint(x: -66.585, y: 1.125), control1: NSPoint(x: 56.055, y: 19.1849999999999), control2: NSPoint(x: -15.415, y: 41.765))
path.closeSubpath()
let shapeNode = SKShapeNode(path: path)
shapeNode.fillColor = .red
shapeNode.lineWidth = 10
shapeNode.lineJoin = .round
shapeNode.strokeColor = .white
addChild(shapeNode)
}
}
let sceneView = SKView(frame: CGRect(x:0 , y:0, width: 640, height: 480))
if let scene = GameScene(fileNamed: "GameScene") {
scene.scaleMode = .aspectFill
sceneView.presentScene(scene)
}
PlaygroundSupport.PlaygroundPage.current.liveView = sceneView
What am I doing wrong here?
This looks odd to me:
path.move(to: NSPoint(x: -66.585, y: 1.125))
path.addCurve(to: NSPoint(x: -60.585, y: -41.765), control1: NSPoint(x: -66.585, y: 1.125), control2: NSPoint(x: -65.835, y: -32.735))
You move to (-66.58., 1.125). Then you add a curve where the first point is (-60.585, -41.765), and the first control point is the same as the original point you moved to. It seems like that will cause some odd issues. I'd think you'd want to have the point you move to be the first point of the curve, not its control point.

How to move SKSpriteNode in a curve without rotating the sprite in Swift

I'm trying to move a sprite with a curve.
I got this code:
let path = UIBezierPath()
path.move(to: CGPoint.zero)
path.addQuadCurve(to: CGPoint(x: ball.position.x+200, y: ball.position.y+50), controlPoint: CGPoint(x: ball.position.x+100, y: ball.position.y+200))
ball.run(SKAction.follow(path.cgPath, speed: 1.0))
I have few questions:
1 - why my sprite is rotating while moving, and if I can control this rotation?
2 - any idea why the ball is moving only small part of the way and very slow and not a smooth moving (10-20 seconds)?
Does anyone have any idea how this code works?
All the answers I found were related to older Swift version that had different method.
At last I've found the solution :)
func beizerSprite()
{
// create a bezier path that defines our curve
let path = UIBezierPath()
path.move(to: CGPoint(x: 16,y: 239))
path.addCurve(to:CGPoint(x: 301, y: 239),
controlPoint1: CGPoint(x: 136, y: 373),
controlPoint2: CGPoint(x: 178, y: 110))
// use the beizer path in an action
_playButton.run(SKAction.follow(path.cgPath,
asOffset: false,
orientToPath: true,
speed: 50.0))
}
This move the SKSprite in a curve along the screen.

Scaleable UIBezier in Swift 3

I'm trying to design a scaleable UIBezier.
This is my Swift Code:
#IBDesignable
class AnimatedRingView: UIView {
override func draw(_ rect: CGRect) {
//// Color Declarations
let strokeColor = UIColor(red: 1.000, green: 0.706, blue: 0.004, alpha: 1.000)
//// bezier Drawing
let bezierPath = UIBezierPath()
bezierPath.move(to: CGPoint(x: 48.93, y: 266.07))
bezierPath.addCurve(to: CGPoint(x: 48.93, y: 53.93), controlPoint1: CGPoint(x: -9.64, y: 207.49), controlPoint2: CGPoint(x: -9.64, y: 112.51))
bezierPath.addCurve(to: CGPoint(x: 261.07, y: 53.93), controlPoint1: CGPoint(x: 107.51, y: -4.64), controlPoint2: CGPoint(x: 202.49, y: -4.64))
bezierPath.addCurve(to: CGPoint(x: 261.07, y: 266.07), controlPoint1: CGPoint(x: 319.64, y: 112.51), controlPoint2: CGPoint(x: 319.64, y: 207.49))
bezierPath.addLine(to: CGPoint(x: 261.07, y: 266.07))
bezierPath.lineCapStyle = .round;
bezierPath.lineJoinStyle = .round;
strokeColor.setStroke()
bezierPath.lineWidth = 10
bezierPath.stroke()
}
}
This is the result:
My problem:
To save time, I created the UIBezierPath by PaintCode:
And as you can see above, we have a width and height for the UIBezierPath, however, the code is displayed points of UIBezierPath ...
How can I create a size variable to assign a value via code (or inspector) that automatically identify the points of UIBezierPath?
Is this possible? Need help!
Given you creating a Circular Arc, using Bezier Curves for that purpose seems like overkill.
With UIBezierPath, you can use this function:
func addArcWithCenter(_ center: CGPoint,
radius radius: CGFloat,
startAngle startAngle: CGFloat,
endAngle endAngle: CGFloat,
clockwise clockwise: Bool)
and set a radius, along with the requisite start and end angles.
If you really must use cubic Béziers for the purpose, you should use the magic number kappa which equals 0.5522847498, and that is the control points for a circle built out of cubic Béziers.
I have placed a small demo of what I'm describing on my web site at:
http://www.trilithon.com/download/AnimatedRingView.zip
Hope That Helps.
Bezier objects do not support attaching variables to their dimensions, but you can use Oval object with custom start/end angles.
— PaintCode Support