0

I just started experimenting with blocks and I am interesting in creating my own methods that take completion blocks. Until now I read a tutorial from appCoda and a this post from stackoverflow: Custom Completion Block For My Own Method

And here is my attempt to implement it an example:

#import "MainViewController.h"

@interface MainViewController ()
{
    UIImageView *animationView;
}
typedef void(^myCompletion)(BOOL);
@end

@implementation MainViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    //Set the backgroun image
    UIImageView *backgroundImage = [[UIImageView alloc] initWithFrame:CGRectMake(0, 20.0f, [[UIScreen mainScreen] bounds].size.width, [[UIScreen mainScreen] bounds].size.height - 20.f)];
    [backgroundImage setImage:[UIImage imageNamed:@"chalk_board_texture_by_blueamnesiac-d4h76qm.png"]];
    [self.view addSubview:backgroundImage];


    [self myMethod:^(BOOL finished) {
        if(finished){
            [animationView setImage:[UIImage imageNamed:@"Animation60.png"]];
        }
    }];
}

-(void) myMethod:(myCompletion) compblock{
    //do stuff
    //Get the imges name into array
    NSArray *imageNames = @[@"Animation1.png", @"Animation2.png", @"Animation3.png", @"Animation4.png",
                            @"Animation5.png", @"Animation6.png", @"Animation7.png", @"Animation8.png",
                            @"Animation9.png", @"Animation10.png", @"Animation11.png", @"Animation12.png",
                            @"Animation13.png", @"Animation14.png", @"Animation15.png", @"Animation16.png",
                            @"Animation17.png", @"Animation18.png", @"Animation19.png", @"Animation20.png",
                            @"Animation21.png", @"Animation22.png", @"Animation23.png", @"Animation24.png",
                            @"Animation25.png", @"Animation26.png", @"Animation27.png", @"Animation28.png",
                            @"Animation29.png", @"Animation30.png", @"Animation31.png", @"Animation32.png",
                            @"Animation33.png", @"Animation34.png", @"Animation35.png", @"Animation36.png",
                            @"Animation37.png", @"Animation38.png", @"Animation39.png", @"Animation40.png",
                            @"Animation41.png", @"Animation42.png", @"Animation43.png", @"Animation44.png",
                            @"Animation45.png", @"Animation46.png", @"Animation47.png", @"Animation48.png",
                            @"Animation49.png", @"Animation50.png", @"Animation51.png", @"Animation52.png",
                            @"Animation53.png", @"Animation54.png", @"Animation55.png", @"Animation56.png",
                            @"Animation57.png", @"Animation58.png", @"Animation59.png", @"Animation60.png",];

    //move image objects by name to mutable array
    NSMutableArray *images = [[NSMutableArray alloc] init];
    for (int i = 0; i < imageNames.count; i++) {
        [images addObject:[UIImage imageNamed:[imageNames objectAtIndex:i]]];
    }

    //Create the image horlder for the animation using the window size
    int animationViewWidth = [[UIScreen mainScreen] bounds].size.width - 20.0f;
    int animationViewHeight = animationViewWidth * 1.38;
    animationView = [[UIImageView alloc]initWithFrame:CGRectMake(10.0f, 20.0f, animationViewWidth, animationViewHeight)];
    animationView.animationImages = images;
    animationView.animationDuration = 0.5;
    animationView.animationRepeatCount = 1;
    [self.view addSubview:animationView];
    [animationView startAnimating];

    //completion finished set bool to yes
    compblock(YES);
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}


@end

The bug in the completion handler is that it doesn't set the background image after the animation finishes. Thank in advance!

Until now I found two great answer to the issue: the one from @Rob: - Set the image view's image property to be the 60th image before startAnimating.

and another one from @ian-macdonald that redirects to a great tutorial that we found at the same time: - How to Animate Images in a UIImageView with Completion Handler

But the question remains why can't a block be implemented as listener and when the animation finishes the block triggers the background image change?

Community
  • 1
  • 1
Laur Stefan
  • 1,519
  • 5
  • 23
  • 50
  • 2
    Unrelated, I'd be wary about using `imageNamed` for a bunch of images in an animation sequence unless you were likely to be reusing that animation sequence several times. As the `imageNamed` documentation says: "If you have an image file that will only be displayed once and wish to ensure that it does not get added to the system’s cache, you should instead create your image using `imageWithContentsOfFile:`. This will keep your single-use image out of the system image cache, potentially improving the memory use characteristics of your app." – Rob Feb 16 '15 at 15:27
  • The first array hold the names of the images and the second one holds the mutable array of objectsm, images – Laur Stefan Feb 16 '15 at 15:49
  • I understand. It's just that `imageNamed` caches its images, so even after your animation is done, even after the image view is removed, even after the whole view controller is gone, the image will still be cached in memory. Only use `imageNamed` for images that your app uses repeatedly and would therefore benefit from this caching behavior. Otherwise use `imageWithContentsOfFile`. – Rob Feb 16 '15 at 15:52

2 Answers2

2

I think you don't need the finished block at all. Just call
[animationView setImage:[UIImage imageNamed:@"Animation60.png"]];
BEFORE triggering the animation and it will show the image after the animation finished. Not sure though if there will be a short time where the last image is shown before the animation is started (But I think there's none if you start the animation right away).

Micky
  • 5,578
  • 7
  • 31
  • 55
  • The selected image, image 60 doesn't remain on the screen, it disappear after the animation ends, same problem as before – Laur Stefan Feb 16 '15 at 15:38
  • Why was this down-voted? This seems elegant in its simplicity and, unless the OP wants to go down the rabbit hole of triggering some other even upon completion of the animation, this seems like a valid answer to me. – Rob Feb 16 '15 at 15:39
  • @Laur Stefan Which image does the imageview show after the animation finished then? – Micky Feb 16 '15 at 15:40
  • 1
    @LaurStefan Set the image view's `image` property to be the 60th image before `startAnimating`. – Rob Feb 16 '15 at 15:40
  • Also: have you checked that Animation60.png is a valid path to an existing image? – Micky Feb 16 '15 at 15:41
  • 2
    What is displayed when you remove the animation completely and just call `[animationView setImage:[UIImage imageNamed:@"Animation60.png"]];` instead of `[self myMethod:^(BOOL finished) { if(finished){ [animationView setImage:[UIImage imageNamed:@"Animation60.png"]]; } }];`? – Micky Feb 16 '15 at 16:02
  • If I remove the animation than the background image will be set to the Animation60.png – Laur Stefan Feb 16 '15 at 16:13
  • @Rob so I tested your suggestion on setting the property before I animate it and it seems that it fixed the bug. Vote up from me for the easy.simple approach can you formulate an answer out of it so I can upVote it? – Laur Stefan Feb 16 '15 at 16:18
  • How is that different from what I suggested here? – Micky Feb 16 '15 at 16:22
  • 2
    @LaurStefan I'm quite happy if you just accept this answer. I was merely restating what Kim already pointed out. – Rob Feb 16 '15 at 16:22
1

You're calling compblock immediately after telling the animation to start. The animation hasn't had time to complete.

You could listen for animation completion, or you could just assume that it takes the exact amount of time you request.

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  compblock(YES);
});

You may experience some misaligned frames with this method because the animation may take less or more time, but it'll probably just be a few milliseconds and entirely unnoticeable.


You may be interested in the first hit on Google for uiimageview animation images completion. Relevant code block:

NSMutableArray *images = [[NSMutableArray alloc] init];
NSInteger animationImageCount = 38;
for (NSInteger i = 0; i < animationImageCount; i++) {
    // Images are numbered IndexedImagesInMyAnimation0, 1, 2, etc...
    [images addObject:(id)[UIImage imageNamed:[NSString stringWithFormat:@"IndexedImagesInMyAnimation%d", i]].CGImage];
}

CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"contents"];
animation.calculationMode = kCAAnimationDiscrete;
animation.duration = animationImageCount / 24.0; // 24 frames per second
animation.values = images;
animation.repeatCount = 1;
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
[self.animationImageView.layer addAnimation:animation forKey:@"animation"];
Ian MacDonald
  • 13,472
  • 2
  • 30
  • 51
  • I have replaced compblock(YES); with what you suggested and it doesn't seem to work, these being the the first issue and the second one is regarding the implementation, I know that is not recommended to use dispatch_after dispatch_time not only because the frame rate but because it may cause the interface to freeze and may lead to a crash. – Laur Stefan Feb 16 '15 at 15:48
  • `dispatch_after` will not cause your interface to freeze or crash unless you are dispatching to the main thread and doing a significant process within the block. – Ian MacDonald Feb 16 '15 at 15:49
  • let me test with 0.6 first – Laur Stefan Feb 16 '15 at 15:52
  • it play the animation and after the animation ends the new background image is not set. the image view remains clear – Laur Stefan Feb 16 '15 at 15:54
  • 1
    this looks like a bad workaround. if you want more control over when the animation finished, you should probably not use animationImages but e.g. CAKeyFrameAnimation. – Micky Feb 16 '15 at 15:54
  • I was looking over the suggested linking at the time of your post – Laur Stefan Feb 16 '15 at 16:09