Swift - when or how to implement animations on vie

2019-09-11 00:40发布

问题:

I'm new to Swift and I'm trying to implement a simple game. In this game, once the view is loaded I want periodic animations to happen.

The problem is that I try to animate my buttons with, for instance, button.frame.origin.x += 50, but instead of moving it from the origin to 50px right, it appears at button.frame.origin.x - 50 and goes to its (initial) position. Funnily enough, at one point I show an AlertDialog to the user, and after it is shown the animation starts to happen as I expected.

My problem is the same of this topic, but the accepted answer just didn't solve it for me.

Any thoughts?

Edit: Digging into the code and testing a lot I found out the method where I show the AlertDialog also happens to invalidate my timer. I have two timers: one to update the UI time (a TextField), and the other to perform the animation. Both are scheduling a task. From what I read here, it is not possible to have two timers like that.

A timer object can be registered in only one run loop at a time, although it can be added to multiple run loop modes within that run loop.

Hence, the obvious solution would be to merge the two selectors (functions) into one, so the timer would work for both tasks. However, if I try to update the UI AND perform animations, the animations don't work as expected anymore. I'm just lost on this problem and can't make both things work.

These are my important pieces of code:

override func viewDidAppear(animated: Bool) {
    super.viewDidAppear(animated)
    ...

    resetOrCreateGame()
}

func resetOrCreateGame() {
    // Do stuff to initialize values
    timerToRotate = NSTimer.scheduledTimerWithTimeInterval(2.0, target: self, selector: "rotateBlocks", userInfo: nil, repeats: true)
}

func rotateBlocks() {
    // Calculate positions to rotate
    UIView.animateWithDuration(0.5, delay: 0, options: UIViewAnimationOptions.CurveEaseOut, animations: {
        // Try to update a timer in the UI
        //self.timeLeft.text = String(self.seconds)

        for i in 0 ... 8 {
            println("\(i) before \(self.buttons[i].frame.origin.x) \(self.buttons[i].frame.origin.y)")
            self.buttons[i].frame.origin.x = self.positions[self.indicesToRotate[i]].x
            self.buttons[i].frame.origin.y = self.positions[self.indicesToRotate[i]].y

            println("\(i) after \(self.buttons[i].frame.origin.x) \(self.buttons[i].frame.origin.y)")
        }
        }, completion: { _ in
                println("completed")
            })

If I leave the line self.timeLeft.text = String(self.seconds) commented, the animations work fine. No matter how I try to update the timeLeft, if I do so it screws my animations. I tried to update it in a separate thread, dispatch it to the main thread, or even update it inside the animations closure: it just doesn't work.

回答1:

Try using SKActions to animate.

Set up an SKAction, then on the SpriteKit node you want to animate (replace "node" with the name of the node), and call it action.

Then, call the simple method:

[node runAction:action];

For example, if you want to set up an SKAction to move a node titled button 50 pixels to the right over a timespan of 3 seconds...

SKAction *action = [SKAction moveByX:50.0 y:0.0 duration:3.0];
[button runAction:action];

Heck, if I don't run an action more than once, I'd simply do this:

[button runAction:[SKAction moveByX:50.0 y:0.0 duration:3.0]];

And finally, you can use the runAction:completion: method to not only run your SKAction, but to call an Objective-C block after it's finished, like in this coding example:

SKAction *action = [SKAction moveByX:50.0 y:0.0 duration:3.0];
[button runAction:action completion:^{
     // some code here that runs after an animation completes
}];

And, to non-complicate your code if you want to run a sequence of actions on a node (move a button 50 pixels to the right called button over 3 seconds, then fade it out over 1 second), you can program a sequence of actions into one action, like so:

SKAction *firstAction = [SKAction moveByX:50.0 y:0.0 duration:3.0];
SKAction *secondAction = [SKAction fadeAlphaTo:0.0 duration:1.0];
SKAction *theSequence = [SKAction sequence:[NSArray arrayWithObjects:firstAction, secondAction, nil];
[button runAction:theSequence]

You CAN run another action in the completion block, but the method of using a sequence cleans up code.



回答2:

Finally, I found the solution! The problem for some reason was Auto Layout. After disabling it everything worked as expected.

Hope it helps someone in the future!