UIView.animation only running once? - swift

The following code runs once and it doesn't even animate..it just appears 20 pixels higher. Any idea why? What I want is for the button to move up and down forever.
scrollButton.frame = CGRectMake(CGRectGetMidX(self.view.frame)-20, (self.view.frame.size.height-64)*1-80, 40, 16)
scrollButton.setImage(UIImage(named: "ARROW.png"), forState: .Normal)
scrollButton.tintColor = UIColor.whiteColor()
self.scrollView.addSubview(scrollButton)
UIView.animateWithDuration(1.0, delay: 0, options: UIViewAnimationOptions.Repeat | UIViewAnimationOptions.Autoreverse, animations: {
self.scrollButton.frame.origin.y -= 20
}, completion: nil)

Although this question is quite old I wanted to add some light to new users.
UIView Animations doesn't work well when called from viewDidLoad try calling it instead from viewWillAppear.
Hope it helps.

I am not sure if that is going to work well , you can make two functions and each one has to move the body and then call the other one . You can use a boolean variable to stop the functions work.

Related

NSAnimationContext issue: no animation with isHidden property

I have the following view structure:
In the Date Stack View, I want to hide/unhide the Advanced Stack Viewin an animated way (like the height was reduced to zero).
I searched a lot for a solution and I came up with this:
dateStackView.wantsLayer = true
NSAnimationContext.runAnimationGroup({ context in
context.duration = 0.5
context.allowsImplicitAnimation = true
advancedDateStackView.animator().isHidden = true
self.view.layoutSubtreeIfNeeded()
}, completionHandler: nil)
But it does not work --> no animation
However, if I replace:
advancedDateStackView.animator().isHidden = true
by
advancedDateStackView.animator().alphaValue = 0,
I can see a fadeout animation for the advancedDateStackView. Which is not the desired behaviour, but proves the animation can work.
I also tried self.advancedDateStackView.animator().frame.size.height = 0
but no animation either.
Any help would be appreciated, I struggle to find a solution.
Thanks!!
EDIT to answer the comment below saying my question is a duplicate: Here my code works and allows me to perform a transition with alphaValue. However, what I need, and what does not work, is to perform a transition on isHidden.

How to execute multiple custom animation functions synchronously in Swift 4

I am working on an IOS App that is supposed to help people in a tourist region to make sense of means of transport. To make it crystal clear to them how to get from A to B, routes are animated with Annotation objects. For example once the user chooses to see the route from A to D, a cable car object slides from A to B. Once it finishes, a Shuttle Bus object moves along a road from B to C, followed by a boat sliding from C to D.
So I wrote the following functions.
This one lets a transportMode object (small image of a boat, cable car, etc.) slide in a straight line from A to B or B to A.
func animateLinearRoute(transportMode: TransportAnnot, startcoor:
CLLocationCoordinate2D, destcoor: CLLocationCoordinate2D){
UIView.animate(withDuration: 3, animations:
{
if (transportMode.coordinate.latitude == startcoor.latitude && transportMode.coordinate.longitude == startcoor.longitude) {
transportMode.coordinate = destcoor
} else {
transportMode.coordinate = startcoor
}
})
}
For moving an object along a nonlinear route (usually a road) drawn on a map I use the following function:
// pass mode of transport and coordinates along the travel route
func animateNonLinearRoute(transportMode: TransportAnnot, animroute: [CLLocationCoordinate2D]){
let path = UIBezierPath()
// get start point of route from coordinates and start drawing route
let point = self.mapView.convert(animroute[0], toPointTo: self.mapView)
path.move(to: point)
// translate each coordinate along the route into a point in the view for drawing
for coor in animroute {
let point = self.mapView.convert(coor, toPointTo: self.mapView)
path.addLine(to: point)
}
// create keyframe animation to move annotation along the previously drawn path
let animation = CAKeyframeAnimation(keyPath: "position")
animation.path = path.cgPath
animation.duration = 5.0
animation.isRemovedOnCompletion = false
let transportview = self.mapView.view(for: transportMode)
transportview?.layer.add(animation, forKey: "animate position along path")
transportMode.coordinate = animroute[animroute.count - 1]
CATransaction.commit()
}
Now the full route can consist of an arbitrary chain of these methods. For example the user may chose to get to a point that requires a linear route -> nonlinear route -> linear route -> nonlinear -> nonlinear.
Ultimately the animations need to be executed in a strictly consecutive manner so the user won't be confused (the second animation should not start unless the first one has finished, etc.).
One consideration would be a keyframe animation like this:
UIView.animateKeyframes(withDuration: 8, delay: 0, options: .calculationModeLinear, animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 5/8, animations: {
self.animateNonLinearRoute(transportMode: self.bus, animroute: self.br.coordinates)
})
UIView.addKeyframe(withRelativeStartTime: 5/8, relativeDuration: 3/8, animations: {
self.animateLinearRoute(transportMode: self.cableCar, startcoor: self.lowerstation, destcoor: self.midstation)
})
// dynamically fill up as needed using appropriate relative start times and durations
}, completion: nil)
That doesn't execute the code synchronously though. I guess it conflicts with the timings and keyframes defined within the functions.
I've been messing around with custom completion closures and then put each method in the completion closure of the previous one as well as with dispatch queues. But I don't really seem to understand them because I wasn't able to achieve the desired effects. And as routes get longer nested completion closures don't seem to be an ideal option as they make the program unnecessarily complex. Any suggestions are highly appreciated.
I guess I traced down the problem.
Since animateNonLinearRoute triggers an animation on the CALayer, the function returns after triggering the animation without waiting for its completion. Therefore all functions in the methods completion handler will be executed, after the CALayer Animation HAS BEEN TRIGGERED, NOT AFTER IT HAS FINISHED.
A simple hacky solution would be to wrap the functions that should be executed after the CALayer Animations have finished, into a CATransaction block:
CATransaction.begin()
CATransaction.setCompletionBlock({
self.animateLinearRoute(transportMode: self.cableCar, startcoor: self.lowerstation, destcoor: self.midstation)
})
self.animateNonLinearRoute(transportMode: self.bus, animroute: self.br.coordinates)
CATransaction.commit()
I would love to hear a better explanation from someone who understands multithreading and concurrency in Swift and a suggestion for a clean way to chain several of these function calls.

sceneDidLoad Running Twice

When I run my program. The code I put into "override func sceneDidLoad()" runs two times.
E.g.
Note: I have no idea why this picture is not uploading, but it shows "spawn" happening twice.
This code should only run once when "sceneDidLoad()" is called.
Here is the code for the "sceneDidLoad" function, and for the "testSpawn()" function (which is the specific one that gave the duplicated printout).
class GameScene: SKScene {
var mapTerrain: SKTileMapNode!
override func sceneDidLoad() {
cam = SKCameraNode()
cam.xScale = 1
cam.yScale = 1
//do zoom by change in scale in pinch. (E.g. if they start out 5 units apart and end up 15 units apart, zoom by a factor of 3
self.camera = cam
self.addChild(cam)
cam.position = CGPoint(x: 100, y: 100)
setupLayers()
loadSceneNodes()
setUpUI()
testSpawn()
//print("\(self.frame.width), \(self.frame.height)")
}
func testSpawn(){
let RedLegion = legion(texture: textureRedLegion, moveTo: nil, tag: 1, health: 2)
RedLegion.position = mapTerrain.centerOfTile(atColumn: 0, row: 0)
RedLegion.team = "Red"
unitsLayer.addChild(RedLegion)
legionList.append(RedLegion)
print("spawn")
}
}
Note: Not all of the code is here (like "setUpLayers()"), if needed I can supply it, I just do not think it is neccessary.
Search your whole document for "print("spawn")" just to make sure that is the only time you call the function. Also check for "testSpawn()" to make sure it is only called once. Additionally, instead of relying on this print to count how many times the sceneDidLoad runs, place a print directly within your sceneDidLoad. Finally, check to make sure you are not creating the scene twice.
I've also seen this and submitted a bug report but apple responded saying that it is intended behavior. Apple said that it creates a dummy scene and then creates the actual scene. Before it runs the second time it gets rid of anything done the first time so you shouldn't get any errors from it. The bug is really hard to reproduce, one of my friends working off the same repository that I was but did not experience the bug.
I changed sceneDidLoad to didMoveToView:(SKView *)view if you are looking for a solution to this. Make sure you xcode is up to date.

SpriteKit SKSequence to make UI Buttons appear

In a gameOverScene of my SpriteKit game, I have 3 buttons which appear on the screen.
I'm trying to make them "pop in", one after the other... I figured a neat way was to fade them in from 0 alpha to full.
I'm currently doing it like this:
//animate in buttons
restartButton.alpha = 0
shareButton.alpha = 0
exitButton.alpha = 0
let bringInUIButtons = SKAction.sequence([
SKAction.waitForDuration(1.0),
SKAction(restartButton.runAction(SKAction.fadeInWithDuration(0.3))),
SKAction.waitForDuration(1.0),
SKAction(shareButton.runAction(SKAction.fadeInWithDuration(0.3))),
SKAction.waitForDuration(1.0),
SKAction(exitButton.runAction(SKAction.fadeInWithDuration(0.3))),
])
runAction(bringInUIButtons)
But they all fade in at once... Even though, as part of my sequence I wait for a second in-between each Action.
I even tried this:
//animate in buttons
restartButton.alpha = 0
shareButton.alpha = 0
exitButton.alpha = 0
let bringInUIButtons = SKAction.sequence([
SKAction.waitForDuration(1.0),
SKAction(restartButton.runAction(SKAction.fadeInWithDuration(0.3))),
SKAction.waitForDuration(1.0),
SKAction(shareButton.runAction(SKAction.fadeInWithDuration(0.6))),
SKAction.waitForDuration(1.0),
SKAction(exitButton.runAction(SKAction.fadeInWithDuration(0.9))),
])
runAction(bringInUIButtons)
...and by changing the fade in duration, so each button takes longer to appear, but it doesn't look very good.
Am I using the waitForDuration incorrectly? I figured it would wait before starting the next action in the sequence?
How can I make the next button appear after the first?
I think the problem is that you're having each button run its own "fade in" action, which means they'll execute their actions in parallel. If you want them to appear one at a time in the scene, it's better to have the scene run the action. Here's my solution...
When you instantiate restartButton, shareButton, and exitButton, set their name properties as shown below:
restartButton.name = "restartButton"
shareButton.name = "shareButton"
exitButton.name = "exitButton"
You'll use these name properties to specify the button on which you want to run a "fade in" action using the runAction(_:onChildWithName:) method.
When it's time to fade the buttons in, use this set of actions:
let fadeIn = SKAction.fadeInWithDuration(1)
let wait = SKAction.waitForDuration(1)
let bringInRestartButton = SKAction.runAction(fadeIn, onChildWithName: "restartButton")
let bringInShareButton = SKAction.runAction(fadeIn, onChildWithName: "shareButton")
let bringInExitButton = SKAction.runAction(fadeIn, onChildWithName: "exitButton")
let bringInUIButtons = SKAction.sequence([
bringInRestartButton,
wait,
bringInShareButton,
wait,
bringInExitButton
])
runAction(bringInUIButtons)

Accessing properties of multiple SKShapeNodes

In my program I have a method called addObstacle, which creates a rectangular SKShapeNode with an SKPhysicsBody, and a leftward velocity.
func addObstacle(bottom: CGFloat, top: CGFloat, width: CGFloat){
let obstacleRect = CGRectMake(self.size.width + 100, bottom, width, (top - bottom))
let obstacle = SKShapeNode(rect: obstacleRect)
obstacle.name = "obstacleNode"
obstacle.fillColor = UIColor.grayColor()
obstacle.physicsBody = SKPhysicsBody(edgeLoopFromPath: obstacle.path!)
obstacle.physicsBody?.dynamic = false
obstacle.physicsBody?.affectedByGravity = false
obstacle.physicsBody?.contactTestBitMask = PhysicsCatagory.Ball
obstacle.physicsBody?.categoryBitMask = PhysicsCatagory.Obstacle
obstacle.physicsBody?.usesPreciseCollisionDetection = true
self.addChild(obstacle)
obstacle.runAction(SKAction.moveBy(obstacleVector, duration: obstacleSpeed))
}
In a separate method, called endGame, I want to fade out all the obstacles currently in existence on the screen. All the obstacle objects are private, which makes accessing their properties difficult. If there is only one on the screen, I can usually access it by its name. However, when I say childNodeWithName("obstacleNode")?.runAction(SKAction.fadeAlphaBy(-1.0, duration: 1.0)), only one of the "obstacles" fades away; the rest remain completely opaque. Is there a good way of doing this? Thanks in advance (:
You could probably go with:
self.enumerateChildNodesWithName("obstacleNode", usingBlock: {
node, stop in
//do your stuff
})
More about this method can be found here.
In this example I assumed that you've added obstacles to the scene. If not, then instead of scene, run this method on obstacle's parent node.
And one side note...SKShapeNode is not performant solution in many cases because it requires at least one draw pass to be rendered by the scene (it can't be drawn in batches like SKSpriteNode). If using a SKShapeNode is not "a must" in your app, and you can switch them with SKSpriteNode, I would warmly suggest you to do that because of performance.
SpriteKit can render hundreds of nodes in a single draw pass if you are using same atlas and same blending mode for all sprites. This is not the case with SKShapeNodes. More about this here. Search SO about this topic, there are some useful posts about all this.