DemoViewController is responsible for showing tutorial to the user. Contains animations and timer to repeat gesture demo while ignored by the user. Is instantiated form DataViewController. Is nil-ed, but later respawns on its internal timer. I need it to be completely gone so it is not created again when user returns to the first page.
dataViewController.h
#import "DemoViewController.h"
@property (strong,nonatomic) DemoViewController *demoController;
dataViewController.h
-(void) viewWillAppear:(BOOL)animated {
// demoPageNumber is 0
if ((self.demoController== nil) && ([_pageNumber isEqualToNumber:demoPageNumber])){
self.demoController = [[DemoViewController alloc] initWithView:self.view];
}
}
-(void) viewWillDisappear:(BOOL)animated{
[self.demoController free]; // invalidate timer, nil all internal objects
self.demoController=nil; // should free object
}
DemoViewController.m
-(void) free{
[animationRespawnTimer invalidate];
animationRespawnTimer=nil;
}
-(void) respawnDemoWithSelector:(SEL)selector{
NSLog(@"Timer fired %@", self);
[self resetTimer];
animationRespawnTimer = [NSTimer scheduledTimerWithTimeInterval:10
target:self
selector:selector
userInfo:nil
repeats:NO];
}
-(void) showScrollDemo{
NSLog(@"showScrollDemo fired");
[self stopPreviousAnimations];
scrollHandView.frame = CGRectMake(315.0, 700.0, 100, 100);
scrollHandView.hidden=NO;
scrollHandView.alpha=1;
[UIView animateWithDuration:3.0
delay: 0.0
options: (UIViewAnimationOptionCurveEaseOut |
UIViewAnimationOptionRepeat )
animations:^{
[UIView setAnimationRepeatCount:3];
scrollHandView.frame = CGRectMake(315.0, 300.0, 100, 100);
}
completion:^(BOOL finished){
[UIView animateWithDuration:1.0 delay:0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
scrollHandView.alpha=0;
}
completion:^(BOOL finished){
scrollHandView.hidden=YES;
[self respawnDemoWithSelector: @selector(showScrollDemo)];
}
];
}
];
}
When the page is loaded and if this is the first page, demoController is instantiated, and on exit from the page nil-ed after clean-up (custom free method). According to my understanding this should delete the demoController object with all its contents including the timer. And debug area shows exactly that! Until when on the new page the demoController timer mystically respawns from nowhere with previous object ID.
17:59:14.041 viewWillAppear begin (self.demoController null)
18:00:05.346 viewWillAppear, <DemoViewController: 0x7580310> //demoController created
18:00:15.786 in the demoController method the "showScrollDemo" is fired
18:00:19.834 viewWillAppear end <DemoViewController: 0x7580310>
Page was loaded, demo performed fine. Now I'm flipping the page. viewWillDisappear event is fired.
18:01:17.966 viewWillDisappear begin, send "free" message to demoController
18:01:17.966 "free" was performed from <DemoViewController: 0x7580310>
18:01:34.059 viewWillDisappear end (self.demoController null)
So, the "self.demoController" is null. Then the demoController respawns itself with previous ID
18:02:36.514 Timer fired <DemoViewController: 0x7580310>
Why? Timer can not respawn, it is set to repeats:NO.
I assume that it is the completion block of the animation that calls respawnDemoWithSelector
and creates a new timer.
According to this answer: https://stackoverflow.com/a/9676508/1187415, you can stop all running animations with
[self.view.layer removeAllAnimations];
Alternatively, you can add a boolean property done
to the DemoViewController which is set
to YES
in the free
method, and checked in the completion block of the animation:
if (!self.done)
[self respawnDemoWithSelector: @selector(showScrollDemo)];
UPDATE: The animation blocks capture a strong reference to self
, thus preventing
the object from being deallocated. The standard solution to that "retain-cycle" problem
(assuming that you use ARC) is to use a weak reference to self. That would look like this:
__weak typeof(self) weakSelf = self;
[UIView animateWithDuration:3.0
delay: 0.0
options: (UIViewAnimationOptionCurveEaseOut |
UIViewAnimationOptionRepeat )
animations:^{
[UIView setAnimationRepeatCount:3];
weakSelf.scrollHandView.frame = CGRectMake(315.0, 300.0, 100, 100);
}
completion:^(BOOL finished){
[UIView animateWithDuration:1.0 delay:0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
weakSelf.scrollHandView.alpha=0;
}
completion:^(BOOL finished){
weakSelf.scrollHandView.hidden=YES;
[weakSelf respawnDemoWithSelector: @selector(showScrollDemo)];
}
];
}
];
weakSelf
does not hold a strong reference to the DemoViewController and is set to nil
automatically if the object it points to is deallocated. In that case, all message sent to weakSelf
inside the blocks do just nothing.