timer = [NSTimer bk_timerWithTimeInterval:60 block:^(NSTimer *timer) { ....} repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
+ (id)bk_timerWithTimeInterval:(NSTimeInterval)inTimeInterval block:(void (^)(NSTimer *timer))block repeats:(BOOL)inRepeats
{
NSParameterAssert(block != nil);
return [self timerWithTimeInterval:inTimeInterval target:self selector:#selector(bk_executeBlockFromTimer:) userInfo:[block copy] repeats:inRepeats];
}
+ (void)bk_executeBlockFromTimer:(NSTimer *)aTimer {
void (^block)(NSTimer *) = [aTimer userInfo];
if (block) block(aTimer);
}
If I fold and unfold application after a few minutes, almost always the timer is firing immediately and sometimes it fire with delay.
Why the timer does not always work immediately?
try to add this in "didFinishLaunchingWithOptions" in your Appdelegate
__block UIBackgroundTaskIdentifier locationUpdater =[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:locationUpdater];
locationUpdater=UIBackgroundTaskInvalid;
} ];
Related
I'm using the following code in iOS 10 but when I run it in iOS 9 it crashes. I don't think NSTimer scheduledTimerWithTimeInterval:repeats:block: supports iOS 9. How can I implement a timer that will work in iOS 8 - 10?
static NSTimer* timer = nil;
- (void)documentInteractionControllerWillBeginPreview:(UIDocumentInteractionController *)controller
{
timer = [NSTimer scheduledTimerWithTimeInterval:0.1 repeats:YES block:^(NSTimer * _Nonnull timer) {
[[UIApplication sharedApplication] setStatusBarHidden:YES];
}];
}
-(void)documentInteractionControllerDidEndPreview:(UIDocumentInteractionController *)controller
{
[timer invalidate];
[[UIApplication sharedApplication] setStatusBarHidden:YES];
}
You appear to be using a function introduced in iOS10. Further down the page you linked is a function that was in iOS 2.
https://developer.apple.com/documentation/foundation/nstimer/1408356-timerwithtimeinterval?language=objc
Use that instead.
This fixed it:
static NSTimer* timer = nil;
- (void)documentInteractionControllerWillBeginPreview:(UIDocumentInteractionController *)controller
{
timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(hideStatusBar) userInfo:nil repeats:YES];
}
-(void)hideStatusBar
{
[[UIApplication sharedApplication] setStatusBarHidden:YES];
}
-(void)documentInteractionControllerDidEndPreview:(UIDocumentInteractionController *)controller
{
[timer invalidate];
[[UIApplication sharedApplication] setStatusBarHidden:YES];
}
I'm trying to build an iOS where I can update my location every x seconds and send a notification to update the UI.
I get my location but the update is random. Any ideas how to add the interval ?
here is my code :
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
CLLocation *location = [locations lastObject];
[self sendNotification :#"long"
:[NSString stringWithFormat:#"%.8f",location.coordinate.longitude]];
[self sendNotification :#"lat"
:[NSString stringWithFormat:#"%.8f",location.coordinate.latitude]];
}
Try this :
declared in .h file
#property (strong, nonatomic) NSDate *lastTimestamp;
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
CLLocation *mostRecentLocation = locations.lastObject;
NSLog(#"Current location: %# %#", #(mostRecentLocation.coordinate.latitude), #(mostRecentLocation.coordinate.longitude));
NSDate *now = [NSDate date];
NSTimeInterval interval = self.lastTimestamp ? [now timeIntervalSinceDate:self.lastTimestamp] : 0;
if (!self.lastTimestamp || interval >= 5 * 60)
{
self.lastTimestamp = now;
NSLog(#"update your UI");
}
}
use NSTimer:
//declare global
NSTimer *ShareTimeCheck;
int ShareSecLeft;
implement this method:
-(void)shareTimeChecking{
if (ShareSecLeft==0) {
[ShareTimeCheck invalidate];
ShareTimeCheck=nil;
}else{
ShareSecLeft--;
if (ShareSecLeft==0) {
[ShareTimeCheck invalidate];
ShareTimeCheck=nil;
}
}
}
call this one in ur location update method:
if (ShareSecLeft==0) {
[ShareTimeCheck invalidate];
ShareTimeCheck=nil;
ShareSecLeft=5;
heduledTimerWithTimeInterval:1 target:self selector:#selector(shareTimeChecking) userInfo:nil repeats:YES];
//write ur code to update the ui. or update to server as per ur requirement.
}
In iOS natively we can’t change the interval at which the system updates the user location. IOS updates position regularly every second, if have GPS signal.
If the application is in the foreground, we could simply stop monitoring and again start it after interval, for example using NSTimer. In this case, we have to think about the life of the application. The application runs in the background and during idle stops working.
My final procedure is use NSTimer in the background by using UIApplication:beginBackgroundTaskWithExpirationHandler:. This timer triggers periodically while the application runs in the background. You can view the following example:
#import "AppDelegate.h"
#implementation AppDelegate
BOOL locationStarted = FALSE;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//set default value after application starts
locationStarted = FALSE;
//create CLLocationManager variable
locationManager = [[CLLocationManager alloc] init];
//set delegate
locationManager.delegate = self;
app = [UIApplication sharedApplication];
return YES;
}
//update location
-(void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation{
NSLog(#"Location: %f, %f", newLocation.coordinates.longtitude, newLocation.coordinates.latitude);
}
//run background task
-(void)runBackgroundTask: (int) time{
//check if application is in background mode
if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) {
//create UIBackgroundTaskIdentifier and create a background task, which starts after time
__block UIBackgroundTaskIdentifier bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSTimer* t = [NSTimer scheduledTimerWithTimeInterval:time target:self selector:#selector(startTrackingBg) userInfo:nil repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:t forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
});
}
}
//starts when application switches to background
- (void)applicationDidEnterBackground:(UIApplication *)application
{
//check if application status is in background
if ( [UIApplication sharedApplication].applicationState == UIApplicationStateBackground) {
NSLog(#"start background tracking from appdelegate");
//start updating location with location manager
[locationManager startUpdatingLocation];
}
//change locationManager status after time
[self runBackgroundTask:20];
}
//starts with background task
-(void)startTrackingBg{
//write background time remaining
NSLog(#"backgroundTimeRemaining: %.0f", [[UIApplication sharedApplication] backgroundTimeRemaining]);
//set default time
int time = 60;
//if locationManager is ON
if (locationStarted == TRUE ) {
//stop update location
[locationManager stopUpdatingLocation];
locationStarted = FALSE;
}else{
//start updating location
[locationManager startUpdatingLocation];
locationStarted = TRUE;
//ime how long the application will update your location
time = 5;
}
[self runBackgroundTask:time];
}
//application switches back from background
- (void)applicationWillEnterForeground:(UIApplication *)application
{
locationStarted = FALSE;
//stop updating
[locationManager stopUpdatingLocation];
}
/*** other methods ***/
- (void)applicationWillResignActive:(UIApplication *)application{}
- (void)applicationDidBecomeActive:(UIApplication *)application{}
- (void)applicationWillTerminate:(UIApplication *)application{}
#end
A simpler solution might be to run the timer loop:
__block UIBackgroundTaskIdentifier bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSTimer* t = [NSTimer scheduledTimerWithTimeInterval:time target:self selector:#selector(startTrackingBg) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:t forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
});
I call a method from background thread as follows.
- (void)applicationDidEnterBackground:(UIApplication *)application
{
UIApplication *app = [UIApplication sharedApplication];
//create new uiBackgroundTask
__block UIBackgroundTaskIdentifier bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
//and create new timer with async call:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//run function methodRunAfterBackground
NSTimer* t = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(methodRunAfterBackground) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:t forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
});
}
After 15 seconds, I have to make a call to a number. I use [[UIApplication sharedApplication] openURL:[NSURL URLWithString:[NSString stringWithFormat:#"tel://%#",_number]]];but unable to make a call while application is in background state. Can we make a call from background state? If yes, how?
I'm making a game and would like to use a timer to countdown an event, just like what's seen on Bejeweled. I know that I've to put NSTimer in a NSRunLoop to make it work, since NSTimer is inaccurate. Have tried the following but it still don't work. Please help!
#import ...
NSTimer *_gameTimer;
int secondsLeft;
//some code
//called countdownTimer using [self countdownTimer];
- (void)countdownTimer
{
_gameTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(updateTime:) userInfo:nil repeats:YES];
NSRunLoop *gameRun = [NSRunLoop currentRunLoop];
[gameRun addTimer:_gameTimer forMode:NSDefaultRunLoopMode];
}
- (void)updateTime:(NSTimer *)timer
{
if (secondsLeft>0 && !_gameOver) {
_timerLabel.text = [NSString stringWithFormat:#"Time left: %ds", secondsLeft];
secondsLeft--;
} else if (secondsLeft==0 && !_gameOver) {
// Invalidate timer
[timer invalidate];
[self timerExpire];
}
}
- (void)timerExpire
{
// Gameover
[self gameOver];
[_gameTimer invalidate];
_gameTimer = nil;
}
NSTimer needs to be a local variable so there can only be one instance of the object running through the loop. Here's the code.
- (void)countdownTimer
{
NSTimer *_gameTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(updateTime:) userInfo:nil repeats:YES];
NSRunLoop *gameRun = [NSRunLoop currentRunLoop];
[gameRun addTimer:_gameTimer forMode:NSDefaultRunLoopMode];
if (secondsLeft==0 || _gameOver) {
[_gameTimer invalidate];
_gameTimer = nil;
}
}
- (void)updateTime:(NSTimer *)timer
{
if (secondsLeft>0 && !_gameOver) {
_timerLabel.text = [NSString stringWithFormat:#"Time left: %ds", secondsLeft];
secondsLeft--;
} else if (secondsLeft==0 || _gameOver) {
// Invalidate timer
[timer invalidate];
[self timerExpire];
}
- (void)timerExpire
{
// Gameover
[self gameOver];
}
I have UIView with button Start, UILabel with my NSTimer time and UITableView with time record.
Problem is when I scroll my tableview - NSTimer freeze, why I can do to they work async?
My code:
- (IBAction)startTimer {
_startButton.hidden = YES;
_stopButton.hidden = NO;
isRun = YES;
UIBackgroundTaskIdentifier bgTask = UIBackgroundTaskInvalid;
UIApplication *app = [UIApplication sharedApplication];
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
}];
myTimer = [NSTimer scheduledTimerWithTimeInterval:0.01f
target:self
selector:#selector(tick)
userInfo:nil
repeats:YES];
}
- (void)tick {
_timeSot++;
sot++;
/*some code*/
myTime = [NSString stringWithFormat:#"%#:%#:%#", minStr, secStr, sotStr];
[_timeLabel setText:myTime];
}
Add myTimer to NSRunLoopCommonModes:
myTimer = [NSTimer scheduledTimerWithTimeInterval:0.01f
target:self
selector:#selector(tick)
userInfo:nil
repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:myTimer forMode:NSRunLoopCommonModes];