How can I create a motion animation of a circle in swift - swift

so my Problem is, that I want to have a motion animation of a circle, which is moving from left to right. The circle is created like this:
let point = CGPoint.init(x: 10, y: 10)
let circlePath = UIBezierPath(arcCenter: point, radius: CGFloat(20), startAngle: CGFloat(0), endAngle: CGFloat(Double.pi * 2), clockwise: true)
let shapeLayer = CAShapeLayer()
shapeLayer.path = circlePath.cgPath
How do I create a motion animation of a circle like this?

I have done moving animation on imageView, You can try on any View it will work.
private func animate(_ image: UIImageView) {
UIView.animate(withDuration: 1, delay: 0, options: .curveLinear, animations: {
image.transform = CGAffineTransform(translationX: self.view.frame.width/2-10, y: 0)
}) { (success: Bool) in
image.transform = CGAffineTransform.identity
self.animate(image) // imageAnime is outlet
self.imageAnim.layer.removeAllAnimations() // if you want to remove animation after its done animating once
}
}
call this function Where you want your animation to be done

Related

Using UIViewPropertyAnimator to drive animation along curved path?

I have an interruptible transition that is driven by UIViewPropertyAnimator and I would like to animate a view along a non linear path to its destination.
Doing some research I've found that the way to do this is by using:
CAKeyframeAnimation. However, I'm having issue coordinating the animations also I'm having issue with the CAKeyframeAnimation fill mode.
It appears to be reverse even though I've set it to forwards.
Below is the bezier path:
let bezierPath = self.generateCurve(from: CGPoint(x: self.itemStartFrame.midX, y: self.itemStartFrame.midY), to: CGPoint(x: self.itemEndFrame.midX, y: self.itemEndFrame.midY), bendFactor: 1, thickness: 1)
func generateCurve(from: CGPoint, to: CGPoint, bendFactor: CGFloat, thickness: CGFloat) -> UIBezierPath {
let center = CGPoint(x: (from.x+to.x)*0.5, y: (from.y+to.y)*0.5)
let normal = CGPoint(x: -(from.y-to.y), y: (from.x-to.x))
let normalNormalized: CGPoint = {
let normalSize = sqrt(normal.x*normal.x + normal.y*normal.y)
guard normalSize > 0.0 else { return .zero }
return CGPoint(x: normal.x/normalSize, y: normal.y/normalSize)
}()
let path = UIBezierPath()
path.move(to: from)
let midControlPoint: CGPoint = CGPoint(x: center.x + normal.x*bendFactor, y: center.y + normal.y*bendFactor)
let closeControlPoint: CGPoint = CGPoint(x: midControlPoint.x + normalNormalized.x*thickness*0.5, y: midControlPoint.y + normalNormalized.y*thickness*0.5)
let farControlPoint: CGPoint = CGPoint(x: midControlPoint.x - normalNormalized.x*thickness*0.5, y: midControlPoint.y - normalNormalized.y*thickness*0.5)
path.addQuadCurve(to: to, controlPoint: closeControlPoint)
path.addQuadCurve(to: from, controlPoint: farControlPoint)
path.close()
return path
}
Path visualization:
let shapeLayer = CAShapeLayer()
shapeLayer.path = bezierPath.cgPath
shapeLayer.strokeColor = UIColor.red.cgColor
shapeLayer.lineWidth = 2
containerView.layer.addSublayer(shapeLayer)
Here's the CAKeyframeAnimation that resides inside of the UIViewPropertyAnimator
let curvedPathAnimation = CAKeyframeAnimation(keyPath: "position")
curvedPathAnimation.path = bezierPath.cgPath
curvedPathAnimation.fillMode = kCAFillModeForwards
curvedPathAnimation.calculationMode = kCAAnimationPaced
curvedPathAnimation.duration = animationDuration
self.attatchmentView.layer.add(curvedPathAnimation, forKey: nil)
So far this animation along the correct path, however the animation reverse back to it's starting position and is not in sync with the property animator.
Any suggestions?

Center a bezier path to a UIview?

For the life of me, I cannot center this bezier path (the circle displayed below). The orange view is properly constrained and horizontally aligned, however when I add 3 layers to the orange view using the method below, I cannot seem to center it to the orange view. I declared the orange view as spinnerHolder.
private func createCircleShapeLayer(strokeColor: UIColor, fillColor: UIColor) -> CAShapeLayer {
let layer = CAShapeLayer()
//The farther from 0 x is for this, the more separated the movements of the 3 paths.
let circularPath = UIBezierPath(
arcCenter: CGPoint(x: 0, y: 0),
radius: 30,
startAngle: 0,
endAngle: 2 * CGFloat.pi,
clockwise: true
)
layer.path = circularPath.cgPath
layer.strokeColor = strokeColor.cgColor
layer.lineWidth = 3
layer.fillColor = fillColor.cgColor
layer.lineCap = kCALineCapRound
layer.position = spinnerHolder.center
return layer
}
1- Change arcCenter to
let circularPath = UIBezierPath(arcCenter: CGPoint(x:spinnerHolder.frame.width/2, y:spinnerHolder.frame.height/2),
radius: 30,
startAngle: 0,
endAngle: 2 * CGFloat.pi,
clockwise: true)
2- Comment this
layer.position = spinnerHolder.center
3- Call the method inside viewDidLayoutSubviews
var once = false
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if once {
spinnerHolder.addSublayer(createCircleShapeLayer(,,,,,))
once = false
}
}
calling inside viewDidLayoutSubviews isn't mandatory , add it anytime/anywhere but not before the VC loads
4- Check this Centering CAShapeLayer within UIView Swift
set arcCenter point as
let centerPoint = CGPoint(x:self.viewRect.bounds.midX, y: self.viewRect.bounds.midY)

UIView subview being placed in unexpected position (Swift 4)

I am attempting to add 4 UIView subviews to a UIImageView. These subviews are to act as nodes where a user can tap them and connect to other nodes. For example, they should look
like this. Instead, they are looking like this.
My code for calculating the node positions is as follows:
func initializeConnectionNodes() {
let imageCenter = self.imageView.center
let xOffset = self.imageView.bounds.width/2 //distance from origin x-wise
let yOffset = self.imageView.bounds.height/2 //distance from origin y-wise
self.leftConnectionNode = ConnectionNodeView(connectionPoint: CGPoint(x: imageCenter.x - xOffset, y: imageCenter.y))
self.rightConnectionNode = ConnectionNodeView(connectionPoint: CGPoint(x: imageCenter.x + xOffset, y: imageCenter.y))
self.topConnectionNode = ConnectionNodeView(connectionPoint: CGPoint(x: imageCenter.x, y: imageCenter.y + yOffset))
self.bottomConnectionNode = ConnectionNodeView(connectionPoint: CGPoint(x: imageCenter.x, y: imageCenter.y - yOffset))
self.imageView.addSubview(self.leftConnectionNode!)
self.imageView.addSubview(self.rightConnectionNode!)
self.imageView.addSubview(self.topConnectionNode!)
self.imageView.addSubview(self.bottomConnectionNode!)
}
My code for initialization of the UIView class is as follows:
class ConnectionNodeView: UIView {
var connectionPoint: CGPoint
fileprivate var circleLayer: CAShapeLayer?
init(connectionPoint: CGPoint) {
self.connectionPoint = connectionPoint
super.init(frame: CGRect(x: connectionPoint.x, y: connectionPoint.y, width: 0, height: 0))
let circlePath = UIBezierPath(arcCenter: connectionPoint, radius: CGFloat(8), startAngle: CGFloat(0), endAngle:CGFloat(Double.pi * 2), clockwise: true)
self.circleLayer = CAShapeLayer()
self.circleLayer?.path = circlePath.cgPath
self.circleLayer?.fillColor = UIColor.yellow.cgColor
self.circleLayer?.strokeColor = UIColor.yellow.cgColor
self.circleLayer?.lineWidth = 3.0
self.layer.addSublayer(circleLayer!)
}
It is interesting to note that if I just add the CAShapeLayer as a sublayer to my UIImageView, it looks like it should. However, I need to implement it as a UIView so that I can use gesture recognizers easily. I found a dirty way of fixing it by dividing the coordinates by 100 in the initializer like this:
super.init(frame: CGRect(x: connectionPoint.x/100, y: connectionPoint.y/100, width: 0, height: 0))
However, I would rather do it correctly. What am I missing here? Thank you for your help.
You’re adding the views to your image view, but the imageCenter point is given according to the superview of the image view.
Replace the beginning of the initializeConnectionNodes function with the following:
let xCenter = imageView.bounds.width / 2
let yCenter = imageView.bounds.height / 2
leftConnectionNode = ConnectionNodeView(connectionPoint: CGPoint(x: 0, y: yCenter))
rightConnectionNode = ConnectionNodeView(connectionPoint: CGPoint(x: imageView.bounds.width, y: yCenter))
topConnectionNode = ConnectionNodeView(connectionPoint: CGPoint(x: xCenter, y: 0))
bottomConnectionNode = ConnectionNodeView(connectionPoint: CGPoint(x: xCenter, y: imageView.bounds.height))
Also, you should replace the arc center of circlePath in your ConnectionNodeView subclass with CGPoint.zero, since it works with the coordinate system of the node view itself:
let circlePath = UIBezierPath(arcCenter: .zero, radius: 8, startAngle: 0, endAngle:CGFloat(Double.pi * 2), clockwise: true)

How to add Undo for NSBezierPath with NSUndoManager on Mac OS X with Swift 4?

I'm trying to add Undo function for NSBezierPath.
This function draws circle with NSBezierPath with radius 45. with mouseLocation as center of the circle
Then it draws the NSBezierPath on CAShapeLayer and add to the NSViewController's view.
How should I add Undo function for this method.
Thank you in advance.
func bezierPathMouseUndoTest(mouseLocation: CGPoint, color: NSColor) {
let frame = CGRect(x: CGFloat(0), y: CGFloat(0), width: CGFloat(100), height: CGFloat(100))
// The path should be the entire circle.
let circlePath = NSBezierPath.init()
circlePath.appendArc(withCenter: CGPoint(x: mouseLocation.x, y: mouseLocation.y), radius: (frame.size.width - 10)/2, startAngle: CGFloat(90.0), endAngle: CGFloat(-270.0), clockwise: true) // start from up
// Setup the CAShapeLayer with the path, colors, and line width
circleLayer = CAShapeLayer()
circleLayer.path = circlePath.CGPath
circleLayer.fillColor = NSColor.clear.cgColor
circleLayer.strokeColor = color.cgColor
circleLayer.lineWidth = 5.0;
circleLayer.strokeEnd = 1.0
circleLayer.frame = frame
// Add the circleLayer to the view's layer's sublayers
self.view.layer?.addSublayer(circleLayer)
self.undoManager?.setActionName("Draw Bezier Path")
}
Thank you Willeke.
I added undo for Adding bezierPath to new layer using additional two functions. And changed the above code a little bit.
replace "self.view.layer?.addSublayer(circleLayer)" with
"self.addSublayer(layer: circleLayer)"
remove "self.undoManager?.setActionName("Draw Bezier Path")"
add two functions below
func addSublayer(layer: CALayer) {
undoManager?.registerUndo(withTarget: self, selector: #selector(removeSublayer), object: layer)
self.undoManager?.setActionName("Draw Bezier Path")
self.view.layer?.addSublayer(layer)
}
func removeSublayer(layer: CALayer) {
undoManager?.registerUndo(withTarget: self, selector: #selector(addSublayer), object: layer)
layer.removeFromSuperlayer()
}

Draw a horizontal line and move it

I make a barcode scanner in swift, and i want to add an animation with a horizontal red line on the barcode zone, and i want this line to move up and down ...
i try several code with calayer ... i can draw, but i don't know how to move it (with a repeat)
can you help me ?
drawLine(onLayer: view.layer, fromPoint: CGPoint(x:100, y:100), toPoint: CGPoint(x:400, y:100))
func drawLine(onLayer layer: CALayer, fromPoint start: CGPoint, toPoint end: CGPoint) {
let line = CAShapeLayer()
let linePath = UIBezierPath()
linePath.move(to: start)
linePath.addLine(to: end)
line.path = linePath.cgPath
line.fillColor = nil
line.opacity = 1.0
line.strokeColor = UIColor.red.cgColor
layer.addSublayer(line)
}
You can draw the line with drawing a basic UIView, or any other way just like you did. Then to move it, you can use UIView.animation
Just a simple code piece for moving the UIView(not sure if you can use this move any other thing);
UIView.animate(withDuration: 0.2, delay: 0, options: [.autoreverse, .repeat], animations: {
self.view.transform = CGAffineTransform(translationX: newX, y: newY)
}, completion: nil)