Add continuation block to method - objective-c

I have a singleton that displays status messages:
+ (FFStatusDisplay*) sharedInstance
{
static FFStatusDisplay* sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken,
^{
sharedInstance = [[FFStatusDisplay alloc] init];
});
return sharedInstance;
}
- (void) showStatus:(NSString*)message Timeout:(float)timeout Controller:(UIViewController*)controller
{
if (![self isDisplaying])
{
[self setIsDisplaying:YES];
[self setStatusAlert:[UIAlertController alertControllerWithTitle:nil message:message preferredStyle:UIAlertControllerStyleAlert]];
[controller presentViewController:[self statusAlert] animated:YES completion:
^{
[NSTimer scheduledTimerWithTimeInterval:timeout target:self selector:#selector(timerExpired:) userInfo:nil repeats:NO];
}];
}
}
- (void)timerExpired:(NSTimer *)timer
{
[[self statusAlert] dismissViewControllerAnimated:YES completion:nil];
[self setStatusAlert:nil];
[self setIsDisplaying:NO];
}
I would like to modify showStatus so that I can optionally call it with a completion block that executes after the message is removed by the timer. I think the declaration should be something like this:
- (void) showStatus:(NSString*)message Timeout:(float)timeout Controller:(UIViewController*)controller withBlock:(nullable id (^)(void))block;
But I don't know how to call the block in the body of the implementation. What I've been trying isn't right:
{
if (![self isDisplaying])
{
[self setIsDisplaying:YES];
[self setStatusAlert:[UIAlertController alertControllerWithTitle:nil message:message preferredStyle:UIAlertControllerStyleAlert]];
[controller presentViewController:[self statusAlert] animated:YES completion:
^{
[NSTimer scheduledTimerWithTimeInterval:timeout target:self selector:#selector(timerExpired:) userInfo:nil repeats:NO];
}];
}
block; //this isn't right
}
Can someone show me how to do this?
Thanks

In Objective-C, you call a block like a C function:
block();
That being said, if you want it to fire after your timer has fired, you'll need to add some mechanism to hang onto the block and call it from your timerExpired: method

Related

Why NSTimer doesn't stop with invalidate?

I make a Cocoa application.I create a NSTimer,it can work,but can't stop with invalidate.The following is my code.Anyone else can help me?
#implementation Document
{
NSTimeInterval elapsedTime;
NSTimer *timer;
}
- (IBAction)start:(id)sender
{
elapsedTime = 0.002f;
dispatch_async(dispatch_get_main_queue(), ^{
if(timer == nil)
{
NSLog(#"Starting");
captureFrame = [[CaptureFrame alloc] init];//captureFrame is another object
timer = [NSTimer scheduledTimerWithTimeInterval:elapsedTime target:captureFrame selector:#selector(sendSingleImageImage) userInfo:nil repeats:YES];
NSThread *thread = [NSThread currentThread];
NSLog(#"Current thread is%#",thread);// I want to see the thread.
}
else
{
NSLog(#"Exist!");
[timer invalidate];
timer = nil;
}
});
[_stopButton setEnabled:YES];
[_startButton setEnabled:NO];
}
- (IBAction)stop:(id)sender
{
dispatch_async(dispatch_get_main_queue(), ^{
[timer invalidate];
timer = nil;
NSThread *sthread = [NSThread currentThread];
NSLog(#"S thread is %#",sthread);// I want to see the thread
});
[_startcastButton setEnabled:YES];
[_stopButton setEnabled:NO];
NSLog(#"Stopping");
}
I tried use the following method,but didn't work.
dispatch_async(dispatch_get_main_queue(), ^(void)block)
I also tried to find whether two actions in the same thread,and the result is yes.
So please tell me how to stop the NSTimer.
Any help is appreciated,thanks a lot in advance.

Code seem do play as the order as I want

- (void)viewDidLoad {
[super viewDidLoad];
[self pingSplash];
UIViewController *next = [[self storyboard] instantiateViewControllerWithIdentifier:#"ViewController"];
[self.navigationController pushViewController:next animated:YES];
}
I mean to finish pinSplash then pushViewController, but it directly goto ViewController page, even without finishing pingSplash, what is a good way to do that kind of job?
For the pingSplash part:
- (void) pingSplash
{
SKSplashIcon *pingSplashIcon = [[SKSplashIcon alloc] initWithImage:[UIImage imageNamed:#"ping.png"] animationType:SKIconAnimationTypePing];
_splashView = [[SKSplashView alloc] initWithSplashIcon:pingSplashIcon backgroundColor:[UIColor grayColor] animationType:SKSplashAnimationTypeBounce];
_splashView.animationDuration = 5.0f;
[self.view addSubview:_splashView];
[_splashView startAnimation];
}
The pingSplash method returns as soon as it starts the animation which is why you end up pushing the view controller too soon.
There are a few ways to solve this. One way would be to pass a block to the pingSplash method that is run after an appropriate delay.
Here's one way:
Update the pingSplash method to take a completion handler block that is run after the 5 second delay.
- (void) pingSplash:(void (^)(void))completion
{
SKSplashIcon *pingSplashIcon = [[SKSplashIcon alloc] initWithImage:[UIImage imageNamed:#"ping.png"] animationType:SKIconAnimationTypePing];
_splashView = [[SKSplashView alloc] initWithSplashIcon:pingSplashIcon backgroundColor:[UIColor grayColor] animationType:SKSplashAnimationTypeBounce];
_splashView.animationDuration = 5.0f;
[self.view addSubview:_splashView];
[_splashView startAnimation];
if (completion) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_splashView.animationDuration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
completion();
});
}
}
Then update your viewDidLoad:
- (void)viewDidLoad {
[super viewDidLoad];
[self pingSplash:^{
UIViewController *next = [[self storyboard] instantiateViewControllerWithIdentifier:#"ViewController"];
[self.navigationController pushViewController:next animated:YES];
}];
}

Why doesn't my UIActivityIndicator stop spinning?

I have the code below displaying an ActivityIndicator before calling a GCD process. The background process throws a Notification when it's complete or encounters an error. I am calling the stopAnimating method in the error handler but the spinner keeps spinning. Why?
UIActivityIndicatorView *mIndicator;
#interface VC_Main ()
#end
- (void)viewDidLoad
{
[super viewDidLoad];
[NSLog(#"viewDidLoad");
// create indicator for download activity
mIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[mIndicator setCenter:CGPointMake([self getScreen].x /2.0, [self getScreen].y / 2.0)]; // landscape mode
[self.view addSubview:mIndicator];
// fire off a single interval
[NSTimer scheduledTimerWithTimeInterval:0.0
target:self
selector:#selector(timerTask:)
userInfo:nil
repeats:NO];
...
}
- (void) timerTask:(NSTimer *) timer
{
NSLog(#"DEBUG: timertask timeout");
[mIndicator startAnimating];
...
}
// if there is an error parsing xml downloaded from server, it notifies here
- (void) xmlError:(NSNotification *)note
{
NSLog(#"error parsing xml");
[mIndicator stopAnimating]; // this doesn't work
// fire off a refresh using retry timeout
[NSTimer scheduledTimerWithTimeInterval:TIMEOUT_RETRY_MINS
target:self
selector:#selector(timerTask:)
userInfo:nil
repeats:NO];
NSLog(#"will retry in %d", TIMEOUT_RETRY_MINS);
}
As with every UIKit call, you need to do it on the main thread.
Just do:
dispatch_async(dispatch_get_main_queue(), ^{
[mIndicator stopAnimating];
});
and it should work
Perhaps you retry too soon?
scheduledTimerWithTimeInterval:TIMEOUT_RETRY_MINS
It's supposed to be seconds, not minutes.

Check if an animation image is in an image view at a given time

I have an image view that has two animation images, occuring in 1-second intervals. I want to run some methods when my image view is displaying one of those two images
I already tried doing:
if(self.imageViewThatPerformsAnimation.image == [UIImage imageNamed: #"someImage"])
[self doSomeMethod];
but when I tried this and ran it, [self doSomeMethod]; always ran, and not just when the image view was displaying that one image.
I'm thinking about having a timer that changes a boolean value every one second then saying
if (booleanValue==YES)
[self doSomeMethod]
It's just that I feel there may be a better way.
If you wanted to use a NSTimer, it might look like:
#interface MyViewController ()
{
NSTimer *_timer;
NSArray *_images;
NSInteger _currentImageIndex;
}
#end
#implementation MyViewController
#synthesize imageview = _imageview;
- (void)viewDidLoad
{
[super viewDidLoad];
_images = [NSArray arrayWithObjects:
[UIImage imageNamed:#"imgres-1.jpg"],
[UIImage imageNamed:#"imgres-2.jpg"],
[UIImage imageNamed:#"imgres-3.jpg"],
[UIImage imageNamed:#"imgres-4.jpg"],
nil];
_currentImageIndex = -1;
[self changeImage];
// Do any additional setup after loading the view.
}
- (void)changeImage
{
_currentImageIndex++;
if (_currentImageIndex >= [_images count])
_currentImageIndex = 0;
self.imageview.image = [_images objectAtIndex:_currentImageIndex];
if (_currentImageIndex == 0)
[self doSomething];
}
- (void)startTimer
{
if (_timer) {
[_timer invalidate];
_timer = nil;
}
_timer = [NSTimer timerWithTimeInterval:1.0
target:self
selector:#selector(changeImage)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSDefaultRunLoopMode];
}
- (void)stopTimer
{
[_timer invalidate];
_timer = nil;
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self startTimer];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[self stopTimer];
}

handling location event with didFinishLaunchingWithOptions in background mode or after app was terminated

I call startMonitoringSignificantLocationChanges in location manager init.
I expect that if there is a location event didFinishLaunchingWithOptions will be launched and execute the following code. (including background mode and if app was terminated)
The app freezes (black screen when i try to launch it after a few hours in the background in different location)
Probably i'm doing something wrong when i get a location event. I would appreciate if someone has an idea what's the problem.
By the way, there is no way to simulate this kind of location event but physically move to a different location with different cell tower. is there? ... :(
LocationController.m :
- (id)init
{
self = [super init];
if (self != nil) {
self.locationManager = [[[CLLocationManager alloc] init] autorelease];
self.locationManager.delegate = self;
self.locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters;
[self.locationManager startUpdatingLocation];
[self.locationManager startMonitoringSignificantLocationChanges];
[NSTimer scheduledTimerWithTimeInterval:10 target:self selector:#selector(stop) userInfo:nil repeats:NO];
}
return self;
}
appdelegate.m :
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
id locationValue = [launchOptions objectForKey:UIApplicationLaunchOptionsLocationKey];
if (locationValue)
{
[[LocationController sharedInstance] startMonitoringSignificantLocationChanges];
UIApplication *app = [UIApplication sharedApplication];
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
[self updateLocationService];
return YES;
}
}
- (void) updateLocationService {
[[LocationController sharedInstance] start];
[NSTimer scheduledTimerWithTimeInterval:2 target:self selector:#selector(stop) userInfo:nil repeats:NO];
}
- (void) stop {
[[LocationController sharedInstance] stop];
}
Thank you!
Your didFinishLaunchingWithOptions: method does not return anything if there is no UIApplicationLaunchOptionsLocationKey set.
Just move the return statement outside your if clause:
if (locationValue)
{
...
}
return YES;