How to CGPath or SKShapeNode Rotation - ios

I want to add rotated elliptical paths to one center point. Like image below but random rotated elliptical.
Draw these elliptical orbits with,
roadShape = SKShapeNode(ellipseInRect: centeredRect)
roadShape.strokeColor = UIColor.greenColor()
self.parent!.addChild(roadShape)
need to make small particles follow those elliptical orbits, thats why I need a path.
var followPath = SKAction.followPath(roadShape.path, asOffset: false, orientToPath: true, duration: 10);
particle.runAction(followPath)
I used SKShapeNode to draw and get the path property. But unfortunately SKShapeNode doesn't have any origin or anchorPoint property so I couldn't decent rotate to SKShapeNode. Do you have any suggestion to make it rotate or different way.
Thanks in advance.

let roadShape1 = SKShapeNode(ellipseInRect: CGRectMake(-100, -25, 200, 50))
roadShape1.strokeColor = UIColor.greenColor()
roadShape1.lineWidth = 3
roadShape1.position = CGPoint(x:frame.midX, y: frame.midY)
addChild(roadShape1)
let roadShape2 = SKShapeNode(ellipseInRect: CGRectMake(-100, -25, 200, 50))
roadShape2.strokeColor = UIColor.greenColor()
roadShape2.lineWidth = 3
let action2 = SKAction.rotateByAngle(CGFloat(M_PI)*0.75, duration:0)
roadShape2.runAction(action2)
roadShape2.position = CGPoint(x:frame.midX, y: frame.midY)
addChild(roadShape2)
let roadShape3 = SKShapeNode(ellipseInRect: CGRectMake(-100, -25, 200, 50))
roadShape3.strokeColor = UIColor.greenColor()
roadShape3.lineWidth = 3
let action3 = SKAction.rotateByAngle(CGFloat(M_PI)*0.25, duration:0)
roadShape3.runAction(action3)
roadShape3.position = CGPoint(x:frame.midX, y: frame.midY)
addChild(roadShape3)
You can also use applyTransform to apply the rotation to the bezierPath
instead of the SKShapeNode as follow:
let roadPath1 = UIBezierPath(ovalInRect: CGRect(x: -100, y: -25, width: 200, height: 50))
let roadPath2 = UIBezierPath(ovalInRect: CGRect(x: -100, y: -25, width: 200, height: 50))
roadPath2.applyTransform(CGAffineTransformMakeRotation(45.0 * CGFloat(M_PI) / 180))
let roadPath3 = UIBezierPath(ovalInRect: CGRect(x: -100, y: -25, width: 200, height: 50))
roadPath3.applyTransform(CGAffineTransformMakeRotation(135.0 * CGFloat(M_PI) / 180))
let roadShape1 = SKShapeNode(ellipseInRect: CGRectMake(-100, -25, 200, 50))
roadShape1.path = roadPath1.CGPath
roadPath1.stroke()
roadShape1.lineWidth = 3
roadShape1.position = CGPoint(x:frame.midX, y: frame.midY)
addChild(roadShape1)
let roadShape2 = SKShapeNode(ellipseInRect: CGRectMake(-100, -25, 200, 50))
roadShape2.path = roadPath2.CGPath
roadPath2.stroke()
roadShape2.lineWidth = 3
roadShape2.position = CGPoint(x:frame.midX, y: frame.midY)
addChild(roadShape2)
let roadShape3 = SKShapeNode(ellipseInRect: CGRectMake(-100, -25, 200, 50))
roadShape3.path = roadPath3.CGPath
roadPath3.stroke()
roadShape3.lineWidth = 3
roadShape3.position = CGPoint(x:frame.midX, y: frame.midY)
addChild(roadShape3)

Related

How to add image to video as overlay and animate it with AVVideoCompositionCoreAnimationTool

I wanted to create a video timeline for my exported videos like in this image so my plan was creating a preview view and export the image from view and added to AVMutableVideoComposition as layer and animate it with AVVideoCompositionCoreAnimationTool but most the resources that I found is either obj-c or didn't fit to my use case. Code is for adding text but I thought I can change it later to image if this works. But I feel like I'm not able to show layers at all.
My app also heavily uses VideoFlint's [Cabbage][2] library for exporting videos and adding watermark so if adding is possible with Cabbage I'm also up for it.
var animationLayer = CALayer()
animationLayer.bounds = .init(x: 0, y: 0, width: 1080, height: 1920)
var titleLayer = CATextLayer()
titleLayer.string = "Test"
titleLayer.font = UIFont.systemFont(ofSize: 30)
titleLayer.bounds = .init(x: 20, y: 200, width: 200, height: 100)
animationLayer.addSublayer(titleLayer)
titleLayer.anchorPoint = .init(x: 0.5, y: 0.5)
let animation = CAKeyframeAnimation(keyPath: #keyPath(CALayer.contentsRect))
animation.duration = 5
animation.calculationMode = CAAnimationCalculationMode.discrete
animation.repeatCount = .infinity
animation.values = [
CGRect(x: 0, y: 0, width: 1, height: 1/3.0),
CGRect(x: 0, y: 1/3.0, width: 1, height: 1/3.0),
CGRect(x: 0, y: 2/3.0, width: 1, height: 1/3.0)
] as [CGRect]
animation.beginTime = AVCoreAnimationBeginTimeAtZero
animation.fillMode = CAMediaTimingFillMode.backwards
animation.isRemovedOnCompletion = false
titleLayer.add(animation, forKey: "test")
var parentLayer = CALayer()
var videoLayer = CALayer()
parentLayer.bounds = .init(x: 0, y: 0, width: 1080, height: 1920)
parentLayer.anchorPoint = .init(x: 0, y: 0)
parentLayer.position = .init(x: 0, y: 0)
videoLayer.bounds = .init(x: 0, y: 0, width: 1080, height: 1920)
parentLayer.addSublayer(videoLayer)
videoLayer.anchorPoint = .init(x: 0.5, y: 0.5)
videoLayer.position = .init(x: 0, y: 0)
videoLayer.backgroundColor = UIColor.red.cgColor
parentLayer.backgroundColor = UIColor.blue.cgColor
animationLayer.anchorPoint = .init(x: 0.5, y: 0.5)
animationLayer.position = .init(x: 0, y: 0)
videoLayer.addSublayer(animationLayer)
videoComposition?.animationTool = AVVideoCompositionCoreAnimationTool.init(postProcessingAsVideoLayer: videoLayer, in: parentLayer)
[2]: https://github.com/VideoFlint/Cabbage

How to set the contentGravity variable of CALayer to 'resizeAspectFill'?

I am trying to set the contentGravity variable of a CALayer but I am not quite sure how to do this. I have tried the following without success:
var layer = CAShapeLayer.init()
layer.contentGravity = .resizeAspectFill // This is not recognised
My UIImageView presents the image taken from the camera of the phone. I then use the Vision Framework to detect rectangles, which return the four corner coordinates of the detected rectangle. I then draw the rectangle in the layer of the UIImageView. The content mode of the UIIMageView is 'Aspect Fill'. My rectangle is currently misaligned and i think it has to do with the contentGravity of the layer. Please see image below:
Rectangle drawn into CAShapelayer:
let rec = UIBezierPath.init()
rec.move(to: CGPoint.init(x: topLeft.x, y: topLeft.y))
rec.addLine(to: CGPoint.init(x: topRight.x, y: topRight.y))
rec.addLine(to: CGPoint.init(x: bottomRight.x, y: bottomRight.y))
rec.addLine(to: CGPoint.init(x: bottomLeft.x, y: bottomLeft.y))
rec.close()
self.layer.opacity = 0.4
self.layer.path = rec.cgPath
self.layer.lineWidth = 5
self.layer.strokeColor = UIColor.yellow.cgColor
self.layer.frame = self.imgPreViewOutlet.bounds
self.imgPreViewOutlet.layer.addSublayer(self.layer)
image show misaligned rectangle drawn in the layer of UIImageView
There is no purpose in setting the content gravity of a CAShapeLayer, as it will in no way affect how the shape is drawn. Content gravity is meaningful only for a layer with content, e.g. a CGImage that is drawn into its contents property.
Here's an example where contentsGravity matters; same image, same layer size, different content gravity:
override func viewDidLoad() {
super.viewDidLoad()
do {
let layer = CALayer()
layer.frame = CGRect(x: 100, y: 100, width: 250, height: 200)
layer.contents = UIImage(named:"smiley")!.cgImage
layer.borderWidth = 1
self.view.layer.addSublayer(layer)
layer.contentsGravity = .center
}
do {
let layer = CALayer()
layer.frame = CGRect(x: 100, y: 300, width: 250, height: 200)
layer.contents = UIImage(named:"smiley")!.cgImage
layer.borderWidth = 1
self.view.layer.addSublayer(layer)
layer.contentsGravity = .resize
}
}
But if we try exactly the same thing with a shape layer, the content gravity makes no difference:
override func viewDidLoad() {
super.viewDidLoad()
do {
let layer = CAShapeLayer()
layer.frame = CGRect(x: 100, y: 100, width: 250, height: 200)
layer.path = CGPath(rect: CGRect(x: 0, y: 0, width: 20, height: 20), transform: nil)
layer.borderWidth = 1
self.view.layer.addSublayer(layer)
layer.contentsGravity = .center
}
do {
let layer = CAShapeLayer()
layer.frame = CGRect(x: 100, y: 300, width: 250, height: 200)
layer.path = CGPath(rect: CGRect(x: 0, y: 0, width: 20, height: 20), transform: nil)
layer.borderWidth = 1
self.view.layer.addSublayer(layer)
layer.contentsGravity = .resize
}
}

SKShapeNode SKPhysicsBody Problems

I have a SKShapeNode that I want to add a SKPhysicsBody to. Here's the code for the SKShapeNode:
let width: CGFloat = 200//self.frame.width
let height: CGFloat = self.frame.height
rightBlocker.path = UIBezierPath(roundedRect: CGRect(x:-width/2, y: -height/2, width: width, height: height), cornerRadius: 50).cgPath
rightBlocker.position = CGPoint(x: -75, y: self.frame.height / 2)
rightBlocker.fillColor = UIColor.white
rightBlocker.strokeColor = UIColor.white
rightBlocker.lineWidth = 10
rightBlocker.physicsBody?.affectedByGravity = false
rightBlocker.physicsBody?.isDynamic = false
addChild(rightBlocker)
I've tried to add a SKPhysicsBody using this line of code:
rightBlocker.physicsBody = SKPhysicsBody(rectangleOf: (rightBlocker.frame.size))
but whenever I do, it completely disappears. What am I doing wrong?

Converting CGPoints between UIKit and SpriteKit woes: UIViews next to SKNodes

There are many posts on this but for whatever reason I can't seem to get the correct sequence of conversions correct. I'm trying to get the UISliders to go directly below the SKLabels.
As you can see in the pictures, one of the sliders doesn't show at all, and the other one is in the completely wrong place (the volume slider is near the seek label):
Here is my current attempt at getting this right, though I've tried a few other configurations that got me nowhere:
class GameScene: SKScene {
let seekSlider = UISlider(frame: CGRect(x: 0, y: 0, width: 200, height: 15))
let volSlider = UISlider(frame: CGRect(x: 0, y: 0, width: 200, height: 15))
override func didMove(to view: SKView) {
removeAllChildren() // Delete this in your actual project.
// Add some labels:
let seekLabel = SKLabelNode(text: "Seek")
seekLabel.setScale(3)
seekLabel.verticalAlignmentMode = .center
seekLabel.position = CGPoint(x: frame.minX + seekLabel.frame.width/2,
y: frame.maxY - seekLabel.frame.height)
let volLabel = SKLabelNode(text: "Volume")
volLabel.setScale(3)
volLabel.verticalAlignmentMode = .center
volLabel.position = CGPoint(x: frame.minX + volLabel.frame.width/2,
y: frame.minY + volLabel.frame.height + volSlider.frame.height)
/* CONVERSION WOES BELOW: */
// Configure sliders:
let seekOrigin = convertPoint(toView: convert(seekLabel.position, from: self))
seekSlider.frame = CGRect(origin: seekOrigin, size: CGSize(width: 200, height: 15))
seekSlider.value = 1
let volOrigin = convertPoint(fromView: CGPoint(x: volLabel.frame.minX, y: volLabel.frame.minY))
seekSlider.frame = CGRect(origin: volOrigin, size: CGSize(width: 200, height: 15))
seekSlider.value = 0
// Scene stuff:
view.addSubview(seekSlider)
view.addSubview(volSlider)
addChild(seekLabel)
addChild(volLabel)
}
}
You're very close! There are two minor issues in your code:
Issue #1
You modify the seekSlider's frame twice instead of modifying the seekSlider then the volSlider.
let volOrigin = convertPoint(fromView: CGPoint(x: volLabel.frame.minX, y: volLabel.frame.minY))
seekSlider.frame = CGRect(origin: volOrigin, size: CGSize(width: 200, height: 15))
seekSlider.value = 0
should be
let volOrigin = convertPoint(fromView: CGPoint(x: volLabel.frame.minX, y: volLabel.frame.minY))
volSlider.frame = CGRect(origin: volOrigin, size: CGSize(width: 200, height: 15))
volSlider.value = 0
Issue #2
The conversion code is not correct.
Using the convertPoint(toView method with the node's position should do the trick:
let seekOrigin = convertPoint(toView: seekLabel.position)
Final Code:
class GameScene: SKScene {
let seekSlider = UISlider(frame: CGRect(x: 0, y: 0, width: 200, height: 15))
let volSlider = UISlider(frame: CGRect(x: 0, y: 0, width: 200, height: 15))
override func didMove(to view: SKView) {
removeAllChildren() // Delete this in your actual project.
// Add some labels:
let seekLabel = SKLabelNode(text: "Seek")
seekLabel.setScale(3)
seekLabel.verticalAlignmentMode = .center
seekLabel.position = CGPoint(x: frame.minX + seekLabel.frame.width/2,
y: frame.maxY - seekLabel.frame.height)
let volLabel = SKLabelNode(text: "Volume")
volLabel.setScale(3)
volLabel.verticalAlignmentMode = .center
volLabel.position = CGPoint(x: frame.minX + volLabel.frame.width/2,
y: frame.minY + volLabel.frame.height + volSlider.frame.height)
// Configure sliders:
let seekOrigin = convertPoint(toView: seekLabel.position)
let offsetSeekOrigin = CGPoint(x: seekOrigin.x, y: seekOrigin.y + 50)
seekSlider.frame = CGRect(origin: offsetSeekOrigin, size: CGSize(width: 200, height: 15))
seekSlider.value = 1
let volOrigin = convertPoint(toView: volLabel.position)
let offsetVolOrigin = CGPoint(x: volOrigin.x, y: volOrigin.y - 50)
volSlider.frame = CGRect(origin: offsetVolOrigin, size: CGSize(width: 200, height: 15))
volSlider.value = 0
// Scene stuff:
view.addSubview(seekSlider)
view.addSubview(volSlider)
addChild(seekLabel)
addChild(volLabel)
}
}
Final Result:

Dots following the path

I am looking for some simple method that will animate dots moving by closed path.
I have some path created with UIBezierPath. Which is represented by CAShapeLayer.
I want to place on this shape dots that will move around this closed path. The problem is that I couldn't find the best method. I do not want to add every dot programmatically. Maybe there is some method where could be use particles.
Path will have different shapes. The most important thing is to please dots with the same distance between each other.
I will be glad for help
Here is an example you can run in playground.
It's based on an article by Matt Long (from a while back) http://www.cimgf.com/2009/10/20/marching-ants-with-core-animation/
import UIKit
import PlaygroundSupport
let container = UIView(frame: CGRect(x: 0, y: 0, width: 600, height: 700))
container.backgroundColor = UIColor.green
let v = UIView(frame: CGRect(x: 100, y: 100, width: 300, height: 300))
v.backgroundColor = UIColor.yellow
let shp = CAShapeLayer()
shp.bounds = v.bounds
let pth = UIBezierPath()
pth.move(to: CGPoint(x: 20, y: 220))
pth.addLine(to: CGPoint(x: 20, y: 40))
pth.addLine(to: CGPoint(x: 40, y: 80))
pth.addLine(to: CGPoint(x: 120, y: 40))
pth.addLine(to: CGPoint(x: 140, y: 40))
pth.close()
shp.strokeColor = UIColor.orange.cgColor
shp.fillColor = UIColor.cyan.cgColor
shp.lineWidth = 3.0
shp.lineDashPattern = [5, 5]
shp.path = pth.cgPath
shp.position = CGPoint(x: 150, y: 150)
v.layer.addSublayer(shp)
container.addSubview(v)
let dashAnim = CABasicAnimation(keyPath: "lineDashPhase")
dashAnim.fromValue = 0
dashAnim.toValue = 15
dashAnim.duration = 0.75
dashAnim.repeatCount = 10000
shp.add(dashAnim, forKey: "lineDashPhase")
PlaygroundPage.current.liveView = container
PlaygroundPage.current.needsIndefiniteExecution = true

Resources