문제

특정 속성이 변경되면 내 모델에서 실행되는 알림이 있습니다.결과적으로 특정 뷰 객체의 선택기는 알림을 포착하여 그에 따라 뷰의 위치를 ​​변경합니다.

알림으로 인해 창의 보기가 특정 방향(항상 수직 또는 수평으로 그리고 항상 창의 표준 단계 크기만큼)으로 이동합니다.사용자 작업으로 인해 여러 알림이 차례로 실행될 수 있습니다.예를 들어 보기를 세 단계 아래로 이동하기 위해 3개의 알림을 보낸 다음 보기를 오른쪽 두 단계로 이동하기 위해 두 개의 알림을 더 보낼 수 있습니다.

문제는 애니메이션을 실행할 때 연속적으로 발생하지 않는다는 것입니다.따라서 이전 예에서는 뷰가 세 칸 아래로 천천히 이동한 다음 알림의 결과로 두 칸 위로 이동하기를 원했지만 대신 대각선으로 새 위치로 이동하게 됩니다.

다음은 두 개의 선택기에 대한 코드입니다(placePlayer는 모델의 현재 정보에 따라 뷰의 위치를 ​​설정합니다).

- (void)moveEventHandler: (NSNotification *) notification
{
    [self placePlayer];

    CABasicAnimation* moveAnimation = [CABasicAnimation animationWithKeyPath:@"position"];
    moveAnimation.duration = 3;
    moveAnimation.fillMode = kCAFillModeForwards;  // probably not necessary
    moveAnimation.removedOnCompletion = NO;        // probably not necessary
    [[self layer] addAnimation:moveAnimation forKey:@"animatePosition"];
}

이 메서드를 여러 번 호출하는 방법에 대한 제안 사항이 있으면 애니메이션이 한 번에 실행되지 않고 단계별로 실행됩니다.감사해요!!

도움이 되었습니까?

해결책 2

제가 구현한 솔루션은 실제로 대기열을 사용합니다.다음은 매우 완전한 설명입니다.

이 모든 작업은 PlayerView라는 뷰 클래스에서 수행됩니다.헤더에는 다음을 포함합니다.

#import "NSMutableArray+QueueAdditions.h"

@interface PlayerView : UIImageView {
        Player* representedPlayer;    // The model object represented by the view
        NSMutableArray* actionQueue;  // An array used as a queue for the actions
        bool animatingPlayer;         // Notes if the player is in the middle of an animation
        bool stoppingAnimation;       // Notes if all animations should be stopped (e.g., for re-setting the game)
        CGFloat actionDuration;       // A convenient way for me to change the duration of all animations
// ... Removed other variables in the class (sound effects, etc) not needed for this example
}

// Notifications
+ (NSString*) AnimationsDidStopNotification;

@property (nonatomic, retain) Player* representedPlayer;
@property (nonatomic, retain, readonly) NSMutableArray* actionQueue;
@property (nonatomic, assign) CGFloat actionDuration;
@property (nonatomic, assign) bool animatingPlayer;
@property (nonatomic, assign) bool stoppingAnimation;
// ... Removed other properties in the class not need for this example

- (void)placePlayer;                                        // puts view where needed (according to the model) without animation
- (void)moveEventHandler:(NSNotification *) notification;   // handles events when the player moves
- (void)rotateEventHandler:(NSNotification *) notification; // handles events when the player rotates
// ... Removed other action-related event handles not needed for this example

// These methods actually perform the proper animations
- (void) doMoveAnimation:(CGRect) nextFrame;
- (void) doRotateAnimation:(CGRect)nextFrame inDirection:(enum RotateDirection)rotateDirection;
// ... Removed other action-related methods not needed for this example

// Handles things when each animation stops
- (void) animationDidStop:(NSString*)animationID 
                 finished:(BOOL)finished 
                  context:(void*)context;

// Forces all animations to stop
- (void) stopAnimation;
@end

여담으로, NSMutableArray+QueueAdditions.h/m의 QueueAdditions 카테고리는 다음과 같습니다:

@interface NSMutableArray (QueueAdditions)
- (id)popObject;
- (void)pushObject:(id)obj;
@end

@implementation NSMutableArray (QueueAdditions)
- (id)popObject
{
    // nil if [self count] == 0
    id headObject = [self objectAtIndex:0];
    if (headObject != nil) {
        [[headObject retain] autorelease]; // so it isn't dealloc'ed on remove
        [self removeObjectAtIndex:0];
    }
    return headObject;
}

- (void)pushObject:(id)obj
{
        [self addObject: obj];
}
@end

다음으로 PlayerView 구현에는 다음이 포함됩니다.

#import "PlayerView.h"
#import <QuartzCore/QuartzCore.h>

@implementation PlayerView

@synthesize actionQueue;
@synthesize actionDuration;
@synthesize animatingPlayer;
@synthesize stoppingAnimation;


// ... Removed code not needed for this example (init to set up the view's image, sound effects, actionDuration, etc)

// Name the notification to send when animations stop
+ (NSString*) AnimationsDidStopNotification
{
        return @"PlayerViewAnimationsDidStop";
}

// Getter for the representedPlayer property
- (Player*) representedPlayer
{
        return representedPlayer;
}

// Setter for the representedPlayer property
- (void)setRepresentedPlayer:(Player *)repPlayer
{
        if (representedPlayer != nil)
        {
                [[NSNotificationCenter defaultCenter] removeObserver:self];
                [representedPlayer release];
        }
        if (repPlayer == nil)
        {
                representedPlayer = nil;
                // ... Removed other code not needed in this example         
        }
        else
        {
                representedPlayer = [repPlayer retain];

                if (self.actionQueue == nil)
                {
                        actionQueue = [[NSMutableArray alloc] init];
                }
                [actionQueue removeAllObjects];
                animatingPlayer = NO;
                stoppingAnimation = NO;

                [[NSNotificationCenter defaultCenter]
                addObserver:self
                selector:@selector(moveEventHandler:)
                name:[Player DidMoveNotification]
                object:repPlayer ];

                [[NSNotificationCenter defaultCenter]
                addObserver:self
                selector:@selector(rotateEventHandler:)
                name:[Player DidRotateNotification]
                object:repPlayer ];
                // ... Removed other addObserver actions and code not needed in this example         
         }
}


// ... Removed code not needed for this example

- (void) placePlayer
{
        // Example not helped by specific code... just places the player where the model says it should go without animation
}


// Handle the event noting that the player moved
- (void) moveEventHandler: (NSNotification *) notification
{
        // Did not provide the getRectForPlayer:onMazeView code--not needed for the example.  But this
        // determines where the player should be in the model when this notification is captured
        CGRect nextFrame = [PlayerView getRectForPlayer:self.representedPlayer onMazeView:self.mazeView];

        // If we are in the middle of an animation, put information for the next animation in a dictionary
        // and add that dictionary to the action queue.
        // If we're not in the middle of an animation, just do the animation        
        if (animatingPlayer)
        {
                NSDictionary* actionInfo = [NSDictionary dictionaryWithObjectsAndKeys:
                                             [NSValue valueWithCGRect:nextFrame], @"nextFrame",
                                             @"move", @"actionType",
                                             @"player", @"actionTarget",
                                             nil];
                [actionQueue pushObject:actionInfo];
        }
        else
        {
                animatingPlayer = YES;  // note that we are now doing an animation
                [self doMoveAnimation:nextFrame];
        }
}


// Handle the event noting that the player rotated
- (void) rotateEventHandler: (NSNotification *) notification
{
        // User info in the notification notes the direction of the rotation in a RotateDirection enum
        NSDictionary* userInfo = [notification userInfo];
        NSNumber* rotateNumber = [userInfo valueForKey:@"rotateDirection"];

        // Did not provide the getRectForPlayer:onMazeView code--not needed for the example.  But this
        // determines where the player should be in the model when this notification is captured
        CGRect nextFrame = [PlayerView getRectForPlayer:self.representedPlayer onMazeView:self.mazeView];

        if (animatingPlayer)
        {
                NSDictionary* actionInfo = [NSDictionary dictionaryWithObjectsAndKeys:
                                             [NSValue valueWithCGRect:nextFrame], @"nextFrame",
                                             @"rotate", @"actionType", 
                                             rotateNumber, @"rotateDirectionNumber",
                                             @"player", @"actionTarget",
                                             nil];
                [actionQueue pushObject:actionInfo];
        }
        else
        {
                enum RotateDirection direction = (enum RotateDirection) [rotateNumber intValue];
                animatingPlayer = YES;
                [self doRotateAnimation:nextFrame inDirection:direction];
        }        
}


// ... Removed other action event handlers not needed for this example


// Perform the actual animation for the move action
- (void) doMoveAnimation:(CGRect) nextFrame
{
        [UIView beginAnimations:@"Move" context:NULL];
        [UIView setAnimationDuration:actionDuration];
        [UIView setAnimationDelegate:self];
        [UIView setAnimationDidStopSelector:@selector(animationDidStop:finished:context:)];
        self.frame = nextFrame;        
        [UIView commitAnimations];
}


// Perform the actual animation for the rotate action
- (void) doRotateAnimation:(CGRect)nextFrame inDirection:(enum RotateDirection)rotateDirection
{
        int iRot = +1;
        if (rotateDirection == CounterClockwise)
        {
                iRot = -1;        
        }

        [UIView beginAnimations:@"Rotate" context:NULL];
        [UIView setAnimationDuration:(3*actionDuration)];
        [UIView setAnimationDelegate:self];
        [UIView setAnimationDidStopSelector:@selector(animationDidStop:finished:context:)];

        CGAffineTransform oldTransform = self.transform;
        CGAffineTransform transform = CGAffineTransformRotate(oldTransform,(iRot*M_PI/2.0));
        self.transform = transform;

        self.frame = nextFrame;

        [UIView commitAnimations];
}



- (void) animationDidStop:(NSString*)animationID 
                 finished:(BOOL)finished 
                  context:(void *)context
{
        // If we're stopping animations, clear the queue, put the player where it needs to go 
        // and reset stoppingAnimations to NO and note that the player is not animating
        if (self.stoppingAnimation)
        {
                [actionQueue removeAllObjects];
                [self placePlayer];
                self.stoppingAnimation = NO;
                self.animatingPlayer = NO;
        }

        else if ([actionQueue count] > 0) // there is an action in the queue, execute it
        {
                NSDictionary* actionInfo = (NSDictionary*)[actionQueue popObject];
                NSString* actionTarget = (NSString*)[actionInfo valueForKey:@"actionTarget"];  
                NSString* actionType = (NSString*)[actionInfo valueForKey:@"actionType"]; 

                // For actions to the player...
                if ([actionTarget isEqualToString:@"player"])
                {
                        NSValue* rectValue = (NSValue*)[actionInfo valueForKey:@"nextFrame"];
                        CGRect nextFrame = [rectValue CGRectValue];

                        if ([actionType isEqualToString:@"move"])
                        {
                                [self doMoveAnimation:nextFrame];
                        }
                        else if ([actionType isEqualToString:@"rotate"])
                        {
                                NSNumber* rotateNumber = (NSNumber*)[actionInfo valueForKey:@"rotateDirectionNumber"];
                                enum RotateDirection direction = (enum RotateDirection) [rotateNumber intValue];
                                [self doRotateAnimation:nextFrame inDirection:direction];
                        }
                        // ... Removed code not needed for this example
                }
                else if ([actionTarget isEqualToString:@"cell"])
                {
                            // ... Removed code not needed for this example
                }

        }
        else // no more actions in the queue, mark the animation as done
        {
                animatingPlayer = NO;
                [[NSNotificationCenter defaultCenter]
                 postNotificationName:[PlayerView AnimationsDidStopNotification]
                 object:self
                 userInfo:[NSDictionary dictionaryWithObjectsAndKeys: nil]];
        }
}



// Make animations stop after current animation by setting stopAnimation = YES
- (void) stopAnimation
{
        if (self.animatingPlayer)
        {
                self.stoppingAnimation = YES;
        }
}


- (void)dealloc {
        if (representedPlayer != nil)
        {
                [[NSNotificationCenter defaultCenter] removeObserver:self];
        }
        [representedPlayer release];
        [actionQueue release];
        // …Removed other code not needed for example
        [super dealloc];
}

@end

설명:

뷰는 모델 객체(플레이어)로부터 적절한 알림을 구독합니다.알림을 캡처할 때 이미 애니메이션을 실행하고 있는지 확인합니다(animatingPlayer 속성 사용).그렇다면 알림에서 정보를 가져와(플레이어가 어떻게 애니메이션되어야 하는지 확인) 해당 정보를 사전에 넣고 해당 사전을 애니메이션 대기열에 추가합니다.현재 진행 중인 애니메이션이 없으면 메서드는 animatingPlayer를 true로 설정하고 적절한 do[Whatever]Animation 루틴을 호출합니다.

각 do[Whatever]Animation 루틴은 setAnimationDidStopSelector를 animationDidStop:finished:context:로 설정하여 적절한 애니메이션을 수행합니다.각 애니메이션이 끝나면 animationDidStop:finished:context:메서드(모든 애니메이션을 즉시 중지해야 하는지 확인한 후)는 적절한 do[Whatever]Animation 메서드를 호출하기 위해 대기열에서 다음 사전을 꺼내고 해당 데이터를 해석하여 대기열의 다음 애니메이션을 수행합니다.대기열에 애니메이션이 없는 경우 해당 루틴은 animatingPlayer를 NO로 설정하고 플레이어가 현재 애니메이션 실행을 적절하게 중지한 시기를 다른 개체가 알 수 있도록 알림을 게시합니다.

그게 다야.더 간단한 방법(?)이 있을 수도 있지만 제겐 이 방법이 꽤 괜찮았습니다.실제 결과를 보고 싶다면 App Store에서 내 Mazin 앱을 확인해 보세요.

감사해요.

다른 팁

여기서 하고 싶은 일은 연속적으로 발생해야 하는 애니메이션 대기열을 설정하고 animationDidStop:finished를 수신할 수 있도록 애니메이션 대리자를 설정하는 것입니다.메시지.이렇게 하면 하나의 애니메이션이 완료되면 대기열의 다음 애니메이션을 시작할 수 있습니다.

아래와 같이 배열의 애니메이션 경로를 따라 여러 지점을 제공하는 것을 고려해야 합니다.

아래 예에서는 y축을 따라 여러 점을 지정하지만 애니메이션이 따라갈 베지어 경로를 지정할 수도 있습니다.

기본 애니메이션과 키 프레임 애니메이션의 주요 차이점은 키 프레임을 사용하면 경로를 따라 여러 지점을 지정할 수 있다는 것입니다.

CAKeyframeAnimation *downMoveAnimation;
downMoveAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.translation.y"];
downMoveAnimation.duration = 12;
downMoveAnimation.repeatCount = 1;
downMoveAnimation.values = [NSArray arrayWithObjects:           
                              [NSNumber numberWithFloat:20], 
                              [NSNumber numberWithFloat:220], 
                              [NSNumber numberWithFloat:290], nil]; 
   downMoveAnimation.keyTimes = [NSArray arrayWithObjects:     
                                  [NSNumber numberWithFloat:0], 
                                  [NSNumber numberWithFloat:0.5], 
                                  [NSNumber numberWithFloat:1.0], nil]; 

   downMoveAnimation.timingFunctions = [NSArray arrayWithObjects:                                    
   [CAMediaTimingFunction     functionWithName:kCAMediaTimingFunctionEaseIn],   
        // from keyframe 1 to keyframe 2
    [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut], nil]; 
   // from keyframe 2 to keyframe 3

   downMoveAnimation.removedOnCompletion = NO;
   downMoveAnimation.fillMode = kCAFillModeForwards;
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top