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
Related
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
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:
I want to hav this kind of animation in my app, a case where an arrow shape such that when clicked it transforms into a mark.
This is what i have at present using the code below
func handleTransform(){
let arrowPath = UIBezierPath()
arrowPath.move(to: CGPoint(x: 50, y: 0))
arrowPath.addLine(to: CGPoint(x: 25, y: 75))
arrowPath.addLine(to: CGPoint(x: 25, y: 75))
arrowPath.addLine(to: CGPoint(x: 25, y: 45))
arrowPath.addLine(to: CGPoint(x: 25, y: 35))
arrowPath.close()
let progressLines = CAShapeLayer()
progressLines.path = arrowPath.cgPath
progressLines.strokeColor = UIColor.black.cgColor
progressLines.fillColor = UIColor.clear.cgColor
progressLines.lineWidth = 10.0
progressLines.lineCap = kCALineCapRound
self.view.layer.addSublayer(progressLines)
let animateStrokeEnd = CABasicAnimation(keyPath: "strokeEnd")
animateStrokeEnd.duration = 3.0
animateStrokeEnd.fromValue = 0.0
animateStrokeEnd.toValue = 1.0
progressLines.add(animateStrokeEnd, forKey: "animate stroke end animation")
}
the flow is illustrated in the images bellow
On click of this image
It transform to this image
I have been able t solve, i did a switch image with animation
I have an app in which there are so many popover and every popover so many objects like UIButtons, UILable,UITableView.
Now I want to create view like popover using UIBezierpathswith upside arrow.
How can I do that in a UIView?
don't want to any third party tool.
Any help will be appreciated.
Check my answer from link here and just change the point values according to your need or path.
Hope it will help you.
I am late but could be helpful to someone
func drawPopoverRadius(text: String) -> UIView {
let layer = CAShapeLayer()
let path = UIBezierPath()
path.move(to: .zero)
path.addLine(to: CGPoint(x: 51, y: 0))
path.addLine(to: CGPoint(x: 51, y: 48))
path.addLine(to: CGPoint(x: 51/2 - 4, y: 48))
path.addLine(to: CGPoint(x: 51/2, y: 52))
path.addLine(to: CGPoint(x: 51/2 + 4, y: 48))
path.addLine(to: CGPoint(x: 0, y: 48))
path.close()
layer.path = path.cgPath
layer.fillColor = UIColor.gray.cgColor
layer.anchorPoint = .zero
layer.name = "popover"
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 51, height: 52))
label.clipsToBounds = false
label.numberOfLines = 0
label.font = NunitoSans.regular.font(size: 14)
label.textColor = .white
label.text = text
label.layer.addSublayer(layer)
return label
}
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)