Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Allowing one method call at a time to a category method ios (@synchronized)

I have a UIViewController and a Category for adding methods to the UIViewController. There is a method in the category:

@implementation UIViewController (AlertAnimationsAndModalViews)
-(void)someAddedMethod
{
    UIView *someView;
    //do some animation with the view that lasts 3 seconds
    //remove the view and return

}

And in any view controller i can call this method

[self someAddedMethod];

However, i only want to allow this method to run one at a time. For example, if i make two calls one after the other

[self someAddedMethod];//call1
[self someAddedMethod];//call2

i want the second call to wait until the first call has completed. I understand that UIView animationWithduration... is run in a seperate thread, and seeing as i cant create iVars in the category i cant really use @synchronized(someObject)..

Any advice?

Thanks in advance!

EDIT

The method looks like this:

 -(void)showTopBannerWithHeight:(CGFloat)height andWidth:(CGFloat)width andMessage:(NSString *)message andDuration:(CGFloat)duration
 {
 
 UILabel *messageLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, -height, width, height)];
[self.view.superview addSubview:messageLabel];


[UIView animateWithDuration:0.5
                      delay:0
                    options: UIViewAnimationOptionBeginFromCurrentState
                 animations:^{
                     messageLabel.frame = CGRectMake(0, 0, SCREEN_WIDTH, height);
                 }
                 completion:^(BOOL finished){
                     
                     [UIView animateWithDuration: 0.5
                                           delay:duration
                                         options: UIViewAnimationOptionBeginFromCurrentState
                                      animations:^{
                                          messageLabel.frame = CGRectMake(0, -height, SCREEN_WIDTH, height);
                                      }
                                      completion:^(BOOL finished){
                                          [messageLabel removeFromSuperview];
                                      }];
                 }];

}

So i show a "banner" from the top of the screen, wait for a duration (CGFloat) then slide the view out of the screen and remove. As this is in a category i can't add instance variables.. so what i want to achieve is that if more than one call to this method is made, i want the first call to execute without waiting, but each call after that to wait until the previous call has finished.

like image 203
Tyler Durden Avatar asked May 23 '13 08:05

Tyler Durden


2 Answers

If its just about the animations you may check if ([someView.layer animationForKey:@"sameKeyAsOnCreation"] == nil). Than you will only add an animation, if it is not currently runnning.

You could also use associated objects to store the state on your own (animation running / not running).

like image 138
calimarkus Avatar answered Oct 11 '22 23:10

calimarkus


Assuming you want to start next animation after previous one has finished. This way you can use some shared NSMutableArray* _animationQueue storage:

-(void)someAddedMethod
{
    NSTimeInterval duration = 3.0;

    void (^animationBlock)() = ^{
        //do some animations here 
        self.view.frame = CGRectOffset(self.view.frame, 40, 0);
    };

    __block void (^completionBlock)(BOOL) = ^(BOOL finished){
        [_animationQueue removeObjectAtIndex:0];
        if([_animationQueue count]>0) {
            [UIView animateWithDuration:duration animations:_animationQueue[0] completion:completionBlock];
        }
    };

    [_animationQueue addObject:animationBlock];
    if([_animationQueue count]==1) {
        [UIView animateWithDuration:duration animations:animationBlock completion:completionBlock];
    }
}

Note, you don't need any @synchronized features since everything goes on main thread.

UPDATE: the code below does exactly you need:

-(void)showTopBannerWithHeight:(CGFloat)height andWidth:(CGFloat)width andMessage:(NSString *)message andDuration:(CGFloat)duration
{
    static NSMutableArray* animationQueue = nil;
    if(!animationQueue) {
        animationQueue = [[NSMutableArray alloc] init];
    }

    void (^showMessageBlock)() = ^{
        UILabel *messageLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, width, height)];
        messageLabel.text = message;
        [self.view.superview addSubview:messageLabel];

        [UIView animateWithDuration: 0.5
                              delay:duration
                            options:UIViewAnimationOptionBeginFromCurrentState
                         animations:^{
                             messageLabel.frame = CGRectOffset(messageLabel.frame, 0, -height);
                         }
                         completion:^(BOOL finished){
                             [messageLabel removeFromSuperview];
                             [animationQueue removeObjectAtIndex:0];
                             if([animationQueue count]>0) {
                                 void (^nextAction)() = [animationQueue objectAtIndex:0];
                                 nextAction();
                             }
                         }];
    };

    [animationQueue addObject:showMessageBlock];
    if([animationQueue count]==1) {
        showMessageBlock();
    }
}
like image 34
onegray Avatar answered Oct 11 '22 23:10

onegray