Animating a view in a real time - xcode

I am trying to create animation that is connected to real time.
I have two points on my screen and an image moves between them. So for example I have an image that is at the Strat point at 12:00 and I want it to be at the end point at 12:10.
func animate_this_image(start: Date, end: Date, imageView: UIImageView, start_point: CGPoint, end_point: CGPoint) {
let duration = end.timeIntervalSince(start)
let frame = CGRect(x: end_point.x, y: end_point.y, width: 60, height: 60).offsetBy(dx: -30, dy: -30)
UIView.animate(withDuration: duration) {
imageView.frame = frame
}
}
My problem is that I want it to be connected to the actual clock. So for example if I close the view at 12:02 and open it at 12:09 I want the image to be at the correct place between the two point (roughly at the end). Instead at the moment the image starts again from the start point. That is happening because I just take the two dates and use the difference as the animation duration.
Anyone that has had similar problem or a suggestion on how I can solve this?
This is connected to a map. I have two points and a poly line between the points. I wanted an image to glide between the points. I could not figure anything easy with the map so I created a view on top of the map that has the image view on. I transfer the points from the map to the other view to get the start and stop points. Maybe there is a better way to do this by using the map?
Cheers,
Jonas

This was easier than I thought... this is my solution
func animate_this_image(start: Date, end: Date, imageView: UIImageView, start_point: CGPoint, end_point: CGPoint) {
// find the total duration and the time left until reaching next point
let duration = end.timeIntervalSince(start)
let time_left = end.timeIntervalSince(Date())
// the percentage is used to calculate how far is left on the line
let percentage = time_left / duration
//calculate the correct point at this time
let x_dist = (end_point.x - start_point.x) * CGFloat(percentage)
let y_dist = (end_point.y - start_point.y) * CGFloat(percentage)
imageView.frame = CGRect(x: end_point.x - x_dist, y: end_point.y - y_dist, width: 60, height: 60).offsetBy(dx: -30, dy: -30)
// define end point
let frame = CGRect(x: end_point.x, y: end_point.y, width: 60, height: 60).offsetBy(dx: -30, dy: -30)
// Then animate it
UIView.animate(withDuration: time_left) {
imageView.frame = frame
}
}

Related

How to Animate a UIImage out of the Screen in Swift

i have searched around the web but nothing could answer my question.
I am trying to animate an UIImageview out of the Screen, set the alpha of it to 0, reset its position and reset the alpha to 1 again.
I can't seem to get the right position where to animate the Image to.
I have set up a gesturerecognizer to drag the image around and when the center of the image is above a certain point, i want to animate it out of screen.
This is the Regocnizercode :
#objc func imageDragged(gestureRecognizer: UIPanGestureRecognizer){
//Current Point where the label is dragged
let draggedLabelPoint = gestureRecognizer.translation(in: view)
//Updating the center of the label : viewcenter +/- currentpoint
matchImageView.center = CGPoint(x: view.bounds.width/2 + draggedLabelPoint.x, y: view.bounds.height/2 + draggedLabelPoint.y)
let xFromCenter = view.bounds.width/2 - matchImageView.center.x
var rotation = CGAffineTransform(rotationAngle: xFromCenter / -500)
let scale = min(100/abs(xFromCenter),1)
var scaledAndRotated = rotation.scaledBy(x: scale, y: scale)
//Transforming the Item respective to the distance from center
matchImageView.transform = scaledAndRotated
if gestureRecognizer.state == .ended {
if matchImageView.center.x < (view.bounds.width/2 - 100) {
animateImageToSide(target: CGPoint(x: view.bounds.width - 200, y: matchImageView.center.y))
matchImageView.alpha = 0
}
if matchImageView.center.x > (view.bounds.width/2 + 100) {
animateImageToSide(target: CGPoint(x: view.bounds.width + 200, y: matchImageView.center.y))
matchImageView.alpha = 0
}
//Reset the scaling variables and recentering the Item after swipe
rotation = CGAffineTransform(rotationAngle: 0)
scaledAndRotated = rotation.scaledBy(x: 1, y: 1)
matchImageView.transform = scaledAndRotated
matchImageView.center = CGPoint(x: view.bounds.width/2, y: view.bounds.height/2)
}
}
I am sorry if the resetting part is irrelevant for you, but im not sure if this could cause this behavior.
And this is my current Animationmethod :
func animateImageToSide(target:CGPoint){
UIImageView.animate(withDuration: 0.5, delay: 0, options: .curveLinear, animations: {self.matchImageView.center = target}) { (success: Bool) in
self.updateImage()
self.fadeInImage()//This is where i set the alpha back to 1
}
}
I am not sure if a CGPoint is what i need to use.
The current behavoir of the image is very weird. Most of the time it snaps to a specific place, regardless of the targetposition. Maybe my attempt on this is entirely wrong.
Thanks for your help !

What is the correct way to determine the bounds of NSBezierPath to ensure correct repainting on macOS

I am having some trouble figuring out the correct way to determine the bounds of NZBezierPath for redrawing correctly.
As you can see from the picture below the thin border line seems to be falling outside the NSBezierPath.bounds rectangle.
The code for handling the object dragging is as follows:
func offsetItemLocationBy(item: DItem, x: CGFloat, y:CGFloat)
{
// tell the display to redraw the old rect
let oldBounds = item.bounds // NSBezierPath.bounds
// since the offset can be generated by both mouse moves
// and moveUp:, moveDown:, etc.. actions, we'll invert
// the deltaY amount based on if the view is flipped or
// not.
let invertDeltaY: CGFloat = self.isFlipped ? -1.0: 1.0
let y1=y*invertDeltaY
item.offsetLocationBy(x: x, y: y1) // Creates a new NSBezierPath
self.setNeedsDisplay(oldBounds)
// invalidate the new rect location so that it'll
// be redrawn
self.setNeedsDisplay(item.bounds)
}
Should I be using some other method to calculate the bounds of NSBezierPath?
OK, I just figured it out - NSBezierPath.bounds does not include the LINE WIDTH.
So to calculate the correct bounds subtract lineWidth/2.0 from x and y and add lineWidth to width and height.
var bounds: NSRect {
let b = path.bounds
let sw = path.lineWidth/2.0
let boundsRect = NSRect(x: b.minX-sw, y: b.minY-sw, width: b.width+2*sw, height: b.height+2*sw)
return boundsRect
}

simplest animation causes high cpu usage and very high energy impact SpriteKit

Here is the simplest animation: a blackhole is rotating in the middle of the screen. These two lines of code increase cpu usage from 3% to 31% and energy impact from low to high (sometimes even very high):
let actionLoop = SKAction.repeatForever(SKAction.rotate(byAngle: CGFloat(360), duration: 1000))
hole.run(actionLoop)
Is this normal? I've read more or less similar discussions but haven't find a clear answer. Here is the whole code and screens:
import SpriteKit
import GameplayKit
class GameScene: SKScene {
// Basic for dynamic sizes step01
var width = CGFloat()
var height = CGFloat()
override func didMove(to view: SKView) {
// Basic for dynamic sizes step02
width = self.frame.size.width
height = self.frame.size.height
// test background color
self.backgroundColor = .yellow
// set blackhole
let hole = SKSpriteNode(imageNamed: "blackhole")
let startPosition = CGPoint(x: 0, y: 0)
hole.size = CGSize(width: width/8, height: width/8)
hole.position = CGPoint(x: startPosition.x, y: startPosition.y)
let actionLoop = SKAction.repeatForever(SKAction.rotate(byAngle: CGFloat(360), duration: 1000))
hole.run(actionLoop)
self.addChild(hole)
}
}
Update: And last but not least: it is not a simulator. I test it on the real device.
There are a couple of things your code does which might have impacted the efficiency.
First, SKAction rotation angles are calculated in radians (for the 360 degrees it should be 2*PI, which is roughly 6.28, not 360). As it stands, your code does lots of calculations to 'over-rotate' the sprite, which is wasteful. A better statement would be:
SKAction.rotate(byAngle: CGFloat.pi*2, duration: 1000)
Second, before rotating the sprite, you scale it by half using the corresponding function, which is also a bit wasteful, since each time the rotation is calculated, the scaling is also re-calculated to produce an accurate result. I'd suggest to pre-render a scaled-down version of the sprite and use that instead to save calculation times and load.

How to make animation adapt to different screen size? Swift

I created this animation in a 4-inch size storyboard. How can I make sure the ratio stays the same when it adapts to let's say an iPhone 6. I'm not talking about Auto-Layout or constraints. I added the animation code below.
This works the way I want it in a 4-inch size, but again, When I resize everything for iPhone 6, Everything looks fine, besides the animation itself. How can I fix this?
#IBAction func chimneySmoke(sender: UIButton) {
let smokeOne = UIImageView()
smokeOne.image = UIImage(named: "smoke1a")
smokeOne.frame = CGRect(x: -145, y: 160, width: 20, height: 20)
self.view.addSubview(smokeOne)
let randomXOffset = CGFloat( arc4random_uniform(40))
let path = UIBezierPath()
path.moveToPoint(CGPoint(x: 140,y: 172))
path.addCurveToPoint(CGPoint(x: 150 + randomXOffset, y: 0), controlPoint1: CGPoint(x: 158 + randomXOffset, y: 90), controlPoint2: CGPoint(x: 146 + randomXOffset, y: 60))
let anim = CAKeyframeAnimation(keyPath: "position")
anim.path = path.CGPath
anim.rotationMode = kCAAnimationRotateAuto
anim.duration = 9.0
smokeOne.layer.addAnimation(anim, forKey: "animate position along path")
}
You accomplish this by making the values as percentages rather than hard coded numbers. To do this, you need a fraction multiplied by frame.width or frame.height. Your fraction, if you have base units (ie. you're developing on a screen that is 320 by 640), should be the value you want divided by the length in that direction. For example if your screen width is 320 and you want the point 145, the value will be 145 / 320 * frame.width so when your screen width becomes different (lets say iPad so 768 points) the point will still be in the same spot on the screen as a percentage.
smokeOne.frame = CGRect(x: -145 / 320 * frame.width, y: 160 / 640 * frame.height, width: 20 / 320 * frame.width, height: 20 / 640 * frame.height)

How to make two lines in UIBezierPath to animate at different speed

Say the following scenario:
I have drawn a quadrilateral shape, which is a mask for a UIView. I denote the shape layer as maskLayer. maskLayer crops the bottom of the UIView asymmetrically.
But then I want to fully reveal my UIView in an animation. The animation should be left side of maskLayer drops down to the bottom of UIView, and .2 sec later my right side of maskLayer also drops down to the bottom of UIView, thus fully reveal the entity of UIView.
My approach is to drop down left line first, then right one as the following code:
//this quadrilateral will put down left corner to the bottom of screen
var path2 = UIBezierPath()
path2.moveToPoint(CGPointZero)
path2.addLineToPoint(CGPoint(x: 0, y: frame.height))
path2.addLineToPoint(CGPoint(x: frame.width, y: frame.height / goldRatio / goldRatio))
path2.addLineToPoint(CGPoint(x: frame.width, y: 0))
path2.closePath()
//this rectangle path will put down both corner to the bottom of screen
//thus fix the view to its original shape
var path3 = UIBezierPath()
path3.moveToPoint(CGPointZero)
path3.addLineToPoint(CGPoint(x: 0, y: frame.height))
path3.addLineToPoint(CGPoint(x: frame.width, y: frame.height))
path3.addLineToPoint(CGPoint(x: frame.width, y: 0))
path3.closePath()
I have spent 2 hours trying to figure it out to no avail. May you please give me some instructions about how to achieve just that.
The initial state is like the following:
The end state is like the following:
I truly appreciate your help!
Easiest way to do this... Cheat.
Don't try to animate the path of the shape it really doesn't need it.
What you should do is something like this.
Create your final state view. Rectangular, at the bottom of the screen with the UI on it etc...
This final state view will not move. It will always be here.
Now create another view and insert it as a sub view underneath the final state view. On this you can add a shape layer with the angular corner cut off.
Now all you need to do is animate the position of this angular view downward until it is completely below the final state view.
If they are the same colour the this will give the effect of animating the path of the shape.
To get the different speeds you could have a rectangluar shape layer rotated to 45 degrees. Then animate it to 0 degrees as the view slides down?
In fact, you could do this with a single shape layer that is rotated and moved.
To do this sort of animation, you would generally use a CADisplayLink (sort of like a timer, but linked to updates of the display rather than some arbitrary interval) to repeatedly change the path associated with desired shape. I think this is easiest if you use a CAShapeLayer and just change the path property of this shape.
To make this work, you need a function that represents the path at a given point of time (or easier, a path at an instant a certain percentageComplete along the animation duration). Since you have a regular shape (constantly the same number of points), you can simply interpolate between some array of startPoints and endPoints.
So, create the shape layer, capture the start time, start the display link, and then for every "tick" of the display link, calculate what percentage of the total animationDuration has passed, and update the shape layer's path accordingly:
class ViewController: UIViewController {
let animationDuration = 2.0
var displayLink: CADisplayLink!
var startTime: CFAbsoluteTime!
var shapeLayer: CAShapeLayer!
let goldRatio: CGFloat = 1.6180339887
var startPoints:[CGPoint]!
var endPoints:[CGPoint]!
override func viewDidLoad() {
super.viewDidLoad()
self.startAnimation()
}
func startAnimation() {
startPoints = [
CGPoint(x: 0, y: view.bounds.size.height),
CGPoint(x: 0, y: view.bounds.size.height - view.bounds.size.height / goldRatio),
CGPoint(x: view.bounds.size.width, y: 0),
CGPoint(x: view.bounds.size.width, y: view.bounds.size.height)
]
endPoints = [
CGPoint(x: 0, y: view.bounds.size.height),
CGPoint(x: 0, y: view.bounds.size.height * 0.75),
CGPoint(x: view.bounds.size.width, y: view.bounds.size.height * 0.75),
CGPoint(x: view.bounds.size.width, y: view.bounds.size.height)
]
assert(startPoints.count == endPoints.count, "Point counts don't match")
createShape()
startDisplayLink()
}
func startDisplayLink() {
displayLink = CADisplayLink(target: self, selector: "handleDisplayLink:")
startTime = CFAbsoluteTimeGetCurrent()
displayLink.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes)
}
func stopDisplayLink() {
displayLink.invalidate()
displayLink = nil
}
func handleDisplayLink(displayLink: CADisplayLink) {
var percent = CGFloat((CFAbsoluteTimeGetCurrent() - startTime) / animationDuration)
if percent >= 1.0 {
percent = 1.0
stopDisplayLink()
}
updatePathBasedUponPercentComplete(percent)
}
func createShape() {
shapeLayer = CAShapeLayer()
shapeLayer.fillColor = UIColor.blueColor().CGColor
shapeLayer.strokeColor = UIColor.clearColor().CGColor
updatePathBasedUponPercentComplete(0.0)
view.layer.addSublayer(shapeLayer)
}
func updatePathBasedUponPercentComplete(percentComplete: CGFloat) {
shapeLayer.path = pathBasedUponPercentComplete(percentComplete, frame: view.frame).CGPath
}
func pathBasedUponPercentComplete(percentComplete: CGFloat, frame: CGRect) -> UIBezierPath {
var path = UIBezierPath()
for i in 0 ..< startPoints.count {
let point = CGPoint(
x: startPoints[i].x + (endPoints[i].x - startPoints[i].x) * percentComplete,
y: startPoints[i].y + (endPoints[i].y - startPoints[i].y) * percentComplete
)
if i == 0 {
path.moveToPoint(point)
} else {
path.addLineToPoint(point)
}
}
path.closePath()
return path
}
}

Resources