简体   繁体   中英

iOS view animation allow user interaction

I am making a game similar to Piano Tiles https://itunes.apple.com/us/app/piano-tiles-dont-tap-white/id848160327?mt=8

you can tap a tile even when the earlier animation is not finished.

Here is my implementation

- (void)moveDown {


    [UIView animateWithDuration:DURATION_MOVE_DOWN delay:0 options:UIViewAnimationOptionAllowUserInteraction animations:^{

        for (Tile *t in self.tiles) {
            if (t.y == NUM_TILES_Y - 1) {
                t.y = 0;
            }
            else {
                t.y++;
            }

            if (t.y == 0) {
                //move down
                //then place it to the top (in the completion handler)
                t.frame = [Tile frameWithX:t.x y:NUM_TILES_Y];
            }
            else {
                //move down
                t.frame = [Tile frameWithX:t.x y:t.y];
            }

        }

    } completion:^(BOOL finished) {

        for (Tile *t in self.tiles) {
            //if user tap while animating, just place the tile at its final position first, (then continue the new animation)
            t.frame = [Tile frameWithX:t.x y:t.y];
        }

        [self assignTouchDontTouchWithRowY:0];

    }];

}

the assignTouchDontTouchWithRowY method is to randomly pick a tile of a row to be TileTypeTouch(black tile) and the rest of the row to be TileTypeDontTouch (white tile)

- (void)assignTouchDontTouchWithRowY:(int)y {
    int touchX = [Helper randomIntInclusiveBetweenLow:0 High:NUM_TILES_X -1];
    for (int x = 0; x < NUM_TILES_X; x++) {
        Tile *t = [self tileWithX:x y:y];
        if (x == touchX) {
            t.type = TileTypeTouch;
        }
        else {
            t.type = TileTypeDontTouch;
        }
    }

}

The problem is that, when I press a tile before the earlier animation is completed, some of the code is not executed (eg coloring a new row, moving down a row, and sometimes the bottom row gets translated above to the top instead of sliding down first, then place at the top)

When I use the option UIViewAnimationOptionBeginFromCurrentState , the user interaction gets canceled when the earlier one is animating. So it's not working either.

Edit:

在此处输入图片说明

In the screenshot, the (0, 1) tile is the second last one tapped, it got translated to the top (without sliding down and being updated its color)

also note that above the (y=1) row, there is another row (y=0) outside the screen. ie there are 5 rows, 4 of which are on screen and the first row (y=0) is at the top outside the screen. When sliding down, the last row (y=4) slide down first, then place it at the top (y=0) in the completion handler

EDIT 3: just in case my logic is not clear:

  1. When tap on the black tile, all tiles move down
  2. for the last row (y=4), I move it down first, then place it at the top(outside screen) in completion handler
  3. if user tap a tile during animation, I firstly immediately place the tiles at the correct position (ie the final state of the old animation), then I start the new animation.

EDIT 4: error is illustrated in the video here

https://drive.google.com/file/d/0B-iP0P7UfFj0Q01neEhaZmtBY2c/edit?usp=sharing

the completion block is executed after the new animation (which happens immediately when you call -moveDown

when will the completion block get executed? we don't know! it's in the queue/future!

so move the clean-up code for the previous animation to the beginning of moveDown (outside animiation, and dont forget to check if (finished) to save some unnecessary work

- (void)moveDown {

    Tile *t = self.tiles[0];
    if (t.layer.animationKeys.count) {
        for (Tile *t in self.tiles) {
            t.frame = [Tile frameWithX:t.x y:t.y];
        }
        [self assignTouchDontTouchWithRowY:0];

    }


    [UIView animateWithDuration:DURATION_MOVE_DOWN delay:0 options:UIViewAnimationOptionAllowUserInteraction animations:^{

        NSLog(@"anim");
        for (Tile *t in self.tiles) {
            if (t.y == NUM_TILES_Y - 1) {
                t.y = 0;
            }
            else {
                t.y++;
            }

            if (t.y == 0) {
                //move down
                //then place it to the top (in the completion handler)
                t.frame = [Tile frameWithX:t.x y:NUM_TILES_Y];
            }
            else {
                //move down
                t.frame = [Tile frameWithX:t.x y:t.y];
            }

        }

    } completion:^(BOOL finished) {

        if (finished) {

            for (Tile *t in self.tiles) {
                if (t.y == 0) {
                    t.frame = [Tile frameWithX:t.x y:t.y];
                }
            }
            [self assignTouchDontTouchWithRowY:0];


        }


    }];

}

Try this:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    CGPoint touchPoint = [touch locationInView:self.view];
    if ([[movingView.layer presentationLayer] hitTest:touchPoint])
    {
        //Do stuff here
    }
}

I got this from here

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM