Consecutive Animations - Watchkit - ios

I'm looking to have a variety of animations for an apple watch app. I'm using a series of images and the method of animation is startAnimatingWithImagesInRange(). Any time I have consecutive animation instructions, only the last one in the code executes. Code looks like:
myImage.setImageNamed("testImage")
myImage.startAnimatingWithImagesInRange(NSRange(location: 0, length: 90), duration: 3, repeatCount: 1)
myImage.startAnimatingWithImagesInRange(NSRange(location: 90, length: 180), duration: 3, repeatCount: 1)
In the above code, only the second animation would play. I even tried putting them in separate functions, so I would be calling each function individually, but it still will only play the last animation in my code. I'm fairly new to this so I'm sure there is a better way but after hours and hours of research I haven't been able to come up with a solution or find one online.

This is easy as 1 + 1 :)
Here is what you want:
myImage.setImageNamed("testImage")
let duration = 3
let repeatCount = 1
Timeline.with(identifier: "MyQueuedAnimation", delay: 0.0, duration: duration * repeatCount, execution: {
myImage.startAnimatingWithImagesInRange(NSRange(location: 0, length: 90), duration: duration, repeatCount: repeatCount)
}, completion: {
myImage.startAnimatingWithImagesInRange(NSRange(location: 90, length: 180), duration: duration, repeatCount: 1)
}).start
TimelineKit - feel free to give me some feedback for my little framework. :)

The watch won't queue up animations for you, so use an NSTimer with a delay equal to the duration of the first animation and start the second animation once the timer fires.

Related

UIViewPropertyAnimator with short animation segments in the beginning

Imagine UIViewPropertyAnimator is setup as follows:
let animator = UIViewPropertyAnimator(duration: 2, curve: .easeInOut) {
myView.center = newCenterPoint
}
For this, I can add another animation that starts with a delay, ends with the main:
animator.addAnimation ({
myView.alpha = 0.5
}, withDelayFactor: 0.5)
So above starts at 1 second and continues for 1 second to finish with the main.
Question
Is there a way to add an animation to UIViewPropertyAnimator that starts at the beginning, but continues for a fraction of the time of the main animator? Starts at 0 and continues only for 1 second?
Why not just use another animator? It's fine for two animators to act on the same view simultaneously as long as their animations don't conflict:
let animator = UIViewPropertyAnimator(duration: 2, curve: .easeInOut) {
self.myView.center.y += 100
}
let animator2 = UIViewPropertyAnimator(duration: 1, curve: .easeInOut) {
self.myView.backgroundColor = .red
}
animator.startAnimation(); animator2.startAnimation()
The animation moves the view downward for two seconds, while turning the view red during the first one second.
(Having said all that, in real life I'd probably use a CAAnimationGroup for this, rather than a property animator.)

iOS Animation Repeat (Completion Block Not Being Called)

I'm trying to get my animation to move back and forth. For example, in a fish tank simulation as such, I am trying to get the fish to move back and forth. I am trying to get this to work infinitely long, however the fish only moves in one direction and the completion block is never being called. Can someone advise me how to call the completion block once the fish reaches the end of the screen or another way to approach this?
Attached is my current code:
UIView.animate(withDuration: fishAnimationDuration, delay: fishAnimationDelay, options: [.allowAnimatedContent, .repeat], animations: {
fish.frame = CGRect(x: fishXPosEnd, y: fishYPos, width: fishWidth, height: fishHeight)
}, completion: { (finished: Bool) in
fish.image = #imageLiteral(resourceName: "fishBack")
UIView.animate(withDuration: fishAnimationDuration, delay: fishAnimationDelay, options: [.allowAnimatedContent], animations: {
fish.frame = CGRect(x: fishXPos, y: fishYPos, width: fishWidth, height: fishHeight)
})
})
The completion block isn't called when repeat is in the options because the repeating animation never completes.
In your case I would remove the repeat option. Then in the completion handler of the 2nd animation, call the method containing these animation blocks so the pair of animations are called again.

Check if SCNNode SCNAction is finished

I have created a SceneKit 3D maze world in which a player can move. Some of the moves like jumping involve moving the camera up and down while changing to view direction over a period of time of several seconds. During this time I would like to ignore taps and swipes by the user that would normally result in other types of movements like turning and moving forward.
I could create a timer that matches the jump duration and sets a Bool but I was hoping for a simplier way of checking the SCNNode for the camera.
Is there a simple way to see if the SCNNode for the camera is no longer running the SCNAction for the jump so I can add this logic in front of other tap and swipe actions?
Or perhaps there is an SCNAction that could set the Bool that I could put at the start and finish of my jump sequence?
Here is my jump code:
let jumpUp: SCNAction = SCNAction.move(to: SCNVector3Make(Float(Int(-yPos)), Float(Int(xPos)), jumpHeight), duration: jumpTime)
let jumpAppex: SCNAction = SCNAction.wait(duration: jumpWaitTime)
let fallDown: SCNAction = SCNAction.move(to: SCNVector3Make(Float(Int(-yPos)), Float(Int(xPos)), cameraHeight), duration: jumpTime)
var lookDown: SCNAction = SCNAction.rotateTo(x: 0, y: 0, z: CGFloat(π), duration: jumpTurnTime)
let noLook: SCNAction = SCNAction.wait(duration: jumpTime*2.0)
var lookBack: SCNAction = SCNAction.rotateTo(x: 0, y: 0, z: 0, duration: jumpTurnTime)
switch playerDirection.direction
{
case .south:
lookDown = SCNAction.rotateTo(x: 0, y: 0, z: CGFloat(southZ), duration: jumpTurnTime)
lookBack = SCNAction.rotateTo(x: CGFloat(π/2), y: 0, z: CGFloat(southZ), duration: jumpTurnTime)
case .north:
lookDown = SCNAction.rotateTo(x: 0, y: 0, z: CGFloat(northZ), duration: jumpTurnTime)
lookBack = SCNAction.rotateTo(x: CGFloat(π/2), y: 0, z: CGFloat(northZ), duration: jumpTurnTime)
case .east:
lookDown = SCNAction.rotateTo(x: 0, y: 0, z: CGFloat(eastZ), duration: jumpTurnTime)
lookBack = SCNAction.rotateTo(x: CGFloat(π/2), y: 0, z: CGFloat(eastZ), duration: jumpTurnTime)
case .west:
lookDown = SCNAction.rotateTo(x: 0, y: 0, z: CGFloat(westZ), duration: jumpTurnTime)
lookBack = SCNAction.rotateTo(x: CGFloat(π/2), y: 0, z: CGFloat(westZ), duration: jumpTurnTime)
}
let sequenceJump = SCNAction.sequence([jumpUp, jumpAppex, fallDown])
let sequenceLook = SCNAction.sequence([lookDown, noLook, lookBack])
mazeScene.mazeCamera.runAction(sequenceJump)
mazeScene.mazeCamera.runAction(sequenceLook)
Thanks
Greg
Actions are pretty weird things, I think.
The idea is heavily inspired (basically 1:1 mapping and 'theft') from cocos2D, where they were used as a way to avoid interacting directly with a game-loop, state and time (sort of). Actions provide a kind of modularity and abstraction for handling both time and the creation and invoking of activity, plus a very primitive handling of their conclusions.
You can see just how similar SpriteKit and SceneKit's Actions are to the original ideas when reading here: http://python.cocos2d.org/doc/programming_guide/actions.html
As a result of the divergent and evolving nature of their creation, someone forgot to give them awareness of their own state, despite the fact they influence some aspects of the state of nodes. It would make near perfect sense that an Action object have a "running" state. But they don't, so far as I know.
Instead, you can check to see if a Node has a given Action, and if it does, it's inferred that the Action is running.
Despite most actions doing something across a duration of time, you also can't query them about how far they've made it through their duration, nor what values they've just fired, or will send next. But there are actions that do this, specifically, to cope with the fact this is missing for all Actions means that if you want this, you need to use a special Action that provides this.
And this kind of recursiveness upon and back to self for facilities that should have been innate is the most annoying part of Actions not having been thought through from the perspective of someone familiar with a timeline and keyframes.
Holistically, and in summary: You can't query an action's state, nor its progress, or the value it just did or will use next. This is, absolutely, ridiculous.
I don't know how this was overlooked in the various evolutions of Actions... they're time management and modular activity creators. It seems so logical to include state and progress reporting within them that... well, I don't know... just bizarre that they don't.
So, to answer your question:
You can either use completion handlers, which are the invoking of some code when an Action finishes, to set values or call other functions or clean up stuff, or whatever you want..
Sequence actions, in an SCNAction.sequence... which is a way to have an Action run sequentially, and inside use some Actions that run blocks of code when you want, that call into setting what you need, when you need, in the sequence of Actions. All of which could be avoided if there was transparency to the values the Actions currently had of both time and properties... but...
You can also use the few special actions that have some awareness of the value they're editing by virtue of the changes made to them. I'm only familiar with the float setting abilities in SKEaseKit, but you probably can do this (if you're a much better coder than me) within SceneKit and SpriteKit. SKEaseKit is hiding a couple of value changing Actions that are super useful.
I use it, for example, like this, wherein this mess changes a value of 0 to 1 across a duration (time), in this case linearly, and each frame (hopefully) updates the .xScale of the node upon which this growAction is run:
let growAction = SKEase.createFloatTween(
start: 0,
ender: 1,
timer: time,
easer: SKEase.getEaseFunction(.curveTypeLinear,
easeType: .easeTypeOut),
setterBlock: {(node, i) in
node.xScale = i}
)
I ended up using a .customAction:
I added a class variable isJumping
then at front of the function code I added:
isJumping = true
and added the SCNAction:
let jumpDone: SCNAction = SCNAction.customAction(duration: 0, action: {_,_ in self.isJumping = false})
then changed the sequence to:
let sequenceLook = SCNAction.sequence([lookDown, noLook, lookBack, jumpDone])
then I just do an if on isJumping to see if the jump movement has completed.
There is a runAction(_:completionHandler:), where you can handle the completion. See the documentation.
So, you can pass completion block and check necessary conditions here:
mazeScene.mazeCamera.runAction(sequenceJump) {
print("Sequence jump is completed")
}

SKAction with duration: 0 doesn't work - SpriteKit (Language: Swift)

I am trying to make a fairly simple running animation by changing 2 images every 0.15 seconds and moving the character slightly up, when it has the running image, and slightly down when it has the other. It would work properly if I have 0.5 seconds as duration, or even 0.1, but when I change it to 0, or even 0.0001 the character doesn't move at all. (the image changing works properly)
let movingUp = SKAction.moveTo(CGPoint(x: self.position.x, y: self.position.y + 10), duration: 0)
let waiting = SKAction.waitForDuration(0.15)
let movingDown = SKAction.moveTo(CGPoint(x: self.position.x, y: self.position.y - 10), duration: 0)
let movingSequence = SKAction.sequence([movingUp, waiting, movingDown])
self.runAction(SKAction.repeatActionForever(movingSequence))
let animateAction = SKAction.animateWithTextures(runningTexturesArray, timePerFrame: 0.15)
let repeatAction = SKAction.repeatActionForever(animateAction)
self.runAction(repeatAction)
I also tried moveBy, got the same result. I need to mention when I set the duration to 0.1 I got a strange behaviour having the character shivering up and down (quicker than it should). It has physics body applied because I want to use collide detection later, but I've set affectedByGravity to false. I was thinking that maybe the physicsWorld has some kind of air, which blocks the character from moving freely while in "vacuum" it could do that.
Thank you in advance for any answer. I'd really appreciate it!

How can we sync WatchKit Timer component with a circular ring anmated image sequence?

I am trying to build a Watch Timer app which shows Timer component counting upwards. And this timer component is surrounded by an image view which is animated using a sequence of 60 images that change with each second counting.
But there is a delay being observed with timer counting to 1 second and then imageView animation is not in sync with exact moment.
I start my Timer and animation using following button code:
#IBAction func clickStartButton() {
self.secondsRing.setBackgroundImageNamed("yellow-outer")
self.watchTimer.setDate(NSDate())
self.secondsRing.startAnimatingWithImagesInRange(
NSRange(location: 0, length: 60),
duration: 60,
repeatCount: 0)
self.watchTimer.start()
}
I have yellow-outer0, yellow-outer1 .... etc. This is collection of 60 images.

Resources