ios noob here: I have an ipad app as an In/Out board posted in the office. Basically, it runs one app; a TableView of 14 people and whether they are in or out of the office. At their desks, people hit a button on a web page to indicate their status when they leave for lunch, meeting or whatever. The ipad app then contacts our webserver every 5 minutes to retrieve an updated status list.
I've found a couple old postings on Stack, one here, which says all downloading must happen in the foreground of the application. The post is from 2011 so wondering if things have changed? I would rather not have the UI locked-up every 5 minutes if someone wants too look at the bottom of the list while a refresh is happening.
That post is about the app being in the background, your use case suggests someone is using the app, and it is in the foreground. You can of course do a web request on a background thread without locking the UI thread. The general pattern for your scenario is, when the view appears or the app becomes active, refresh the data (on a background thread), refresh the table (on the main thread), and then set your timer for an automatic refresh (and disable it when the app goes into the background), and potentially implement some kind of 'pull to refresh' feature (https://github.com/enormego/EGOTableViewPullRefresh).
If you do those things, your data will be up to date when people are viewing the app, and users can guarantee it by pulling to refresh.
Yes! Things have changed. It's now possible (as of iOS 7) to run HTTP requests while the app is backgrounded.
In order to do so, you need to add the value fetch to your app's UIBackgroundModes Info.plist key.
For more details see the iOS App Programming Guide.
After looking through a lot of code and a dizzying array of ways to do this, I really couldn't find a "simple" example. Many examples on the net are pre-ARC, or too complex for my level of understanding. Still other examples hinged on 3rd party libraries which are no longer in development. Still other examples, more up to date, have timeouts of 30 seconds in which everything must be completed (ios7 fetch) which doesn't seem like enough time for a quick download on a busy wi-fi network. Eventually, I did manage to piece together a working sample which does run a background download every 20 seconds. Not sure how to update the UI yet.
AppDelegate.m
#import "bgtask.h"
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
bgtask *b = [[bgtask alloc] initTaskWithURL:#"http://www.google.com" app:application];
return YES;
}
bgtask.h
#import <Foundation/Foundation.h>
#interface bgtask : NSOperation
#property (strong, atomic) NSMutableData *webData;
#property (strong, atomic) UIApplication *myApplication;
- (id) initTaskWithURL:(NSString *)url app:(UIApplication *)application;
#end
bgtask.m
#import "bgtask.h"
#implementation bgtask
UIBackgroundTaskIdentifier backgroundTask;
#synthesize webData = _webData;
#synthesize myApplication = _myApplication;
NSString *mURL;
// connect to webserver and send values. return response data
- (void) webConnect
{
NSURL *myURL = [NSURL URLWithString:mURL];
_webData = [NSData dataWithContentsOfURL:myURL];
if (_webData)
{
// save response data if connected ok
NSLog(#"connetion ok got %ul bytes", [_webData length]);
}
else
{
NSLog(#"connection failed");
//TODO: some error handling
}
}
- (void) timerTask:(NSTimer *) timer
{
backgroundTask = [_myApplication beginBackgroundTaskWithExpirationHandler:
^{
dispatch_async(dispatch_get_main_queue(),
^{
if (backgroundTask != UIBackgroundTaskInvalid)
{
[_myApplication endBackgroundTask:backgroundTask];
backgroundTask = UIBackgroundTaskInvalid;
}
});
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^{
NSLog (#"Running refresh...");
[self webConnect];
dispatch_async(dispatch_get_main_queue(),
^{
if (backgroundTask != UIBackgroundTaskInvalid)
{
[_myApplication endBackgroundTask:backgroundTask];
backgroundTask = UIBackgroundTaskInvalid;
}
});
});
}
- (id) initTaskWithURL:(NSString *)url app:(UIApplication *)application
{
self = [super init];
if (self)
{
// setup repeating refresh task.
// Save url, application for later use
mURL = [[NSString alloc] initWithString:url];
_myApplication = application;
[NSTimer scheduledTimerWithTimeInterval:20.0
target:self
selector:#selector(timerTask:)
userInfo:nil
repeats:YES];
NSLog (#"task init");
}// if self
return (self);
}
Related
I am using NSURLSessionDownloadTask objects on an NSURLSession to allow users to download documents while the app is in the background / device locked. I also want to inform the user that individual downloads have finished through a local notification.
To that end, I am triggering a local notification in the -URLSession:downloadTask:didFinishDownloadingToURL: download task delegate method, however I am wondering if there might be a better place to add the code triggering a notification, since the way Apple explains it, the download task will be passed to the system, and from that I am deriving that those delegates will not be called anymore on the download task's delegate once (or shortly after) the app is backgrounded.
My question: What is the best place to add the code for triggering the local notifications? Has anybody had any previous experience in adding this sort of a functionality to their application?
Answer on your question can be found in Apple documentation URL Loading System Programming Guide:
In iOS, when a background transfer completes or requires credentials,
if your app is no longer running, iOS automatically relaunches your
app in the background and calls the
application:handleEventsForBackgroundURLSession:completionHandler:
method on your app’s UIApplicationDelegate object. This call provides
the identifier of the session that caused your app to be launched.
Your app should store that completion handler, create a background
configuration object with the same identifier, and create a session
with that configuration object. The new session is automatically
reassociated with ongoing background activity. Later, when the session
finishes the last background download task, it sends the session
delegate a URLSessionDidFinishEventsForBackgroundURLSession: message.
Your session delegate should then call the stored completion handler.
If any task completed while your app was suspended, the delegate’s
URLSession:downloadTask:didFinishDownloadingToURL: method is then
called with the task and the URL for the newly downloaded file
associated with it.
As you see it's much more complicated then just set delegate object. By delegate methods you will be notified only if app in foreground mode. In other cases (app in background mode, app is terminated) you need handle AppDelegate methods that are described in above quote.
Also Apple provides example project, that shows how to work with background download/upload tasks. This example will help you to find place where to put "Local Notification" code.
As Visput explained above, this method will be called once the download completes.application:handleEventsForBackgroundURLSession:completionHandler:
This will happen if you use the NSURLSessionConfiguration class with the backgroundSessionConfiguraton. You might be missing that piece.
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration backgroundSessionConfiguration:#"com.BGTransfer"];
sessionConfiguration.HTTPMaximumConnectionsPerHost = 5; // To set the max concurrent connections
It is explained in detail here.
As Suggested by #Gautam Jain u have to use backgroundSessionConfiguration to achieve ur objective.Below i have attached a example ,hope it helps you
DownloadModel.h
#import "AppDelegate.h"
#interface DownloadModel : NSObject<NSURLSessionDelegate,NSURLSessionTaskDelegate,NSURLSessionDownloadDelegate>{
NSString *resp;
}
+(instancetype)shared;
-(NSURLSessionDownloadTask *) downloadTaskWithURL:(NSURL*)url ;
#end
DownloadModel.m
#import "DownloadModel.h"
#interface DownloadModel ()
#property (strong,nonatomic) NSURLSession *downloadSession;
#end
#implementation DownloadModel
+(instancetype)shared{
static dispatch_once_t onceToken;
static DownloadModel *downloader=nil;
dispatch_once(&onceToken, ^{
downloader=[DownloadModel new];
});
return downloader;
}
-(id)init{
self=[super init];
if(self){
NSURLSessionConfiguration *downloadConfig=[NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:#"DownloadDemo"];
// downloadConfig.timeoutIntervalForRequest = 30;
// downloadConfig.timeoutIntervalForResource = 30;
// downloadConfig.HTTPMaximumConnectionsPerHost = 1;
// downloadConfig.sessionSendsLaunchEvents=YES;
downloadConfig.allowsCellularAccess = YES;
downloadConfig.networkServiceType = NSURLNetworkServiceTypeBackground;
// downloadConfig.discretionary = YES;
self.downloadSession=[NSURLSession sessionWithConfiguration:downloadConfig delegate:self delegateQueue:nil];
self.downloadSession.sessionDescription=#"Video Downloader";
}
return self;
}
-(NSURLSessionDownloadTask *) downloadTaskWithURL:(NSURL*)url{
return [self.downloadSession downloadTaskWithURL:url];
}
#pragma mark download delegate
use notification OR Local Notification in this method
- (void)URLSession:(NSURLSession *)session downloadTask(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL(NSURL *)location{
[[NSNotificationCenter defaultCenter] postNotificationName:#"DownloadFinish" object:downloadTask userInfo:nil];
}
For Progress of Download
- (void)URLSession:(NSURLSession *)session downloadTask(NSURLSessionDownloadTask *)downloadTask didWriteData(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{
CGFloat progress=(CGFloat)totalBytesWritten/totalBytesExpectedToWrite;
NSDictionary *userInfo=#{#"progress":#(progress)};
[[NSNotificationCenter defaultCenter] postNotificationName:#"DownloadProgress" object:downloadTask userInfo:userInfo];
}
#pragma mark delegate
-(void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session{
AppDelegate *appdelegate=[[UIApplication sharedApplication] delegate];
if(appdelegate.backgroundSessionCompletionHandler){
appdelegate.backgroundSessionCompletionHandler();
appdelegate.backgroundSessionCompletionHandler=nil;
}
}
#end
AppDelegate.h
#interface AppDelegate : UIResponder <UIApplicationDelegate>
#property (strong, nonatomic) UIWindow *window;
#property (copy ,nonatomic) void(^backgroundSessionCompletionHandler)();
#end
AppDelegate.m
-(void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler{
self.backgroundSessionCompletionHandler=completionHandler;
[DownloadModel shared];
}
ViewController.m Call this Method -(NSURLSessionDownloadTask *) downloadTaskWithURL:(NSURL*)url
- (void)viewDidLoad {
//Add Notification observers to track download progress and call the above method
[DownloadModel shared] downloadTaskWithURL:url];
}
Don't Forget to enable Background Fetch
I've seen hundreds of solutions of how to get a NSTimer to run in the background.
I know that it is possible, just look at apps like Strava and Runkepper that tracks your time when working out.
But what is the best practice solution for doing so? I can't find one unison solution for this.
Also, I would like the NSTimer to be used across different UIViewControllers. How is this done as a best practice?
Thanks in regards! :)
NSTimers don't run in the background. Store the current time and the elapsed time of the timer when you got the background. When you come back to the foreground, you set up a new timer, using those two pieces of information to setup any state or data that needs to reflect the total elapsed time.
To share between viewCOntroller, just have one object implement this timer, and expose a property on it (e.g. elapsedTime) that gets updated every time interval . Then you can have the viewCOntrollers (that have a reference to that object) observe that property for changes.
You Can Try This Code in Your application NSTimers don't run in the background. acceding to apple But We Try forcefully Only 3 mint
AppDelegate.h
#property (nonatomic, unsafe_unretained) UIBackgroundTaskIdentifier backgroundTaskIdentifier;
#property (nonatomic, strong) NSTimer *myTimer;
- (BOOL) isMultitaskingSupported;
- (void) timerMethod:(NSTimer *)paramSender;
AppDelegate.m
- (void)applicationDidEnterBackground:(UIApplication *)application {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
if ([self isMultitaskingSupported] == NO)
{
return;
}
self.myTimer =[NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:#selector(timerMethod:) userInfo:nil
repeats:YES];
self.backgroundTaskIdentifier =[application beginBackgroundTaskWithExpirationHandler:^(void) {
[self endBackgroundTask];
}];
}
pragma mark - NSTimer Process
- (BOOL) isMultitaskingSupported
{
BOOL result = NO;
if ([[UIDevice currentDevice]
respondsToSelector:#selector(isMultitaskingSupported)]){ result = [[UIDevice currentDevice] isMultitaskingSupported];
}
return result;
}
- (void) timerMethod:(NSTimer *)paramSender{
NSTimeInterval backgroundTimeRemaining =
[[UIApplication sharedApplication] backgroundTimeRemaining];
if (backgroundTimeRemaining == DBL_MAX)
{
NSLog(#"Background Time Remaining = Undetermined");
}
else
{
NSLog(#"Background Time Remaining = %.02f Seconds",backgroundTimeRemaining);
}
}
- (void) endBackgroundTask
{
dispatch_queue_t mainQueue = dispatch_get_main_queue(); __weak AppDelegate *weakSelf = self;
dispatch_async(mainQueue, ^(void) { AppDelegate *strongSelf = weakSelf; if (strongSelf != nil){
[strongSelf.myTimer invalidate];
[[UIApplication sharedApplication]
endBackgroundTask:self.backgroundTaskIdentifier];
strongSelf.backgroundTaskIdentifier = UIBackgroundTaskInvalid;
} });
}
As pointed out in the comments, NSTimer won't work in the background, backround execution on iOS is quite tricky and only works in certain cases, check the Apple Docs on the topic, also this is an excellent read to acquire more background knowledge.
As for your case, it sound like you want to use UILocalNotification. As I understand from your comment:
I want to have a timer running while the app is not in the foreground. Just like Apples own timer app.
Apple's timer app uses UILocalNotification. It gives you a way to schedule a notification which will appear at a certain point in time to the user, regardless of whether the app is in the foreground or background! All you have to do in your app is schedule a notification, e.g. like this:
UILocalNotification *localNotification = [[UILocalNotification alloc] init];
localNotification.fireDate = dateTime;
localNotification.alertBody = [NSString stringWithFormat:#"Alert Fired at %#", dateTime];
localNotification.soundName = UILocalNotificationDefaultSoundName;
[[UIApplication sharedApplication] scheduleLocalNotification:localNotification];
Then iOS will handle the rest for you :)
I am creating an application where I am retrieving data from the server like below:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
[self retrievedatafromserver];
dispatch_async(dispatch_get_main_queue(), ^{
//UIUpdation, fetch the image/data from DB and update into your UI
});
});
How do I retrieve data from the server even if application goes to background?
Thanks & Regards
sumana
If Your scope of project is in only iOS 7 then you can use A new background mode which comes in the iOS 7 and onwards. You can fetch the data in background mode without any extra efforts of coding.
[[UIApplication sharedApplication] setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];
Now that your app already knows to initiate background fetch, let’s tell it what to do. The method -(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler will assist in doing so. This method is called every time that a background fetch is performed, and should be included in the AppDelegate.m file. The complete version is provided below:
-(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
UINavigationController *navigationController = (UINavigationController*)self.window.rootViewController;
id topViewController = navigationController.topViewController;
if ([topViewController isKindOfClass:[ViewController class]]) {
[(ViewController*)topViewController insertNewObjectForFetchWithCompletionHandler:completionHandler];
} else {
NSLog(#"Not the right class %#.", [topViewController class]);
completionHandler(UIBackgroundFetchResultFailed);
}
}
Now in your controller. Do like that
- (void)insertNewObjectForFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
NSLog(#"Update the tableview.");
self.numberOfnewPosts = [self getRandomNumberBetween:0 to:4];
NSLog(#"%d new fetched objects",self.numberOfnewPosts);
for(int i = 0; i < self.numberOfnewPosts; i++){
int addPost = [self getRandomNumberBetween:0 to:(int)([self.possibleTableData count]-1)];
[self insertObject:[self.possibleTableData objectAtIndex:addPost]];
}
/*
At the end of the fetch, invoke the completion handler.
*/
completionHandler(UIBackgroundFetchResultNewData);
}
Note :- If you have to give supportability on iOS 6 and below then avoid this approach. Because it's not available.
When your app enters background mode. you can access code for couple of seconds. Suppose the background queue is still performing and you entered background. then you might need to recall the method when app entered foreground. (take a bool variable and check whether the process is completed or not, if process is completed no issues. if not call the method again.).
If you want to make app run in background mode also then you need to request for background run mode in plist. See this link for reference only for these features we can active background run mode and you can active any of them according to you usage http://blogs.innovationm.com/support-for-applications-running-in-background-ios/
I'm really new to iOS and backend development so please bear with my lack of technical knowledge. I apologise for the potentially confusing title but here's my problem:
I'm trying to create an iOS app that allows the user to receive push notifications when the user's selected course's status is OPEN.
The way to check if a selected course status is OPEN is a GET request from my school's API, and a bit of parsing of the JSON response to extract the course status.
So how do I do this GET request constantly to check for the selected course's status, and send the user a push notification when it is OPEN?
It would be great if someone can point me towards a specific direction to research, thank you.
A push notification should come from a server somewhere. I think what you're looking for is a poll with a local notification if you want the app to be doing the work. The downside to this is the app has to be running for it to poll. I recommend watching this WWDC Session to learn more about how it works. To start the request doing the polling, you'd do something like this:
In your interface:
#property (nonatomic, retain) NSTimer *timer;
And in the implementation:
-(void)someMethodSomewhere
{
// Create a timer and automatically start it.
self.timer = [NSTimer scheduledTimerWithTimeInterval:10.0f // some number of seconds
target:self
selector:#selector(checkStatus)
userInfo:nil
repeats:YES];
}
-(void)checkStatus
{
// Perform request, check course status
if (/* course status is open */)
{
UILocalNotification *notification = [[UILocalNotification alloc]init];
notification.alertBody = #"The course is now open";
notification.fireDate = [NSDate date];
[[UIApplication sharedApplication]presentLocalNotificationNow:notification];
// Stop the timer
[self.timer invalidate];
}
}
Edit
To make it run in the background, you should probably read this document. The upshot is that you need to override the application:performFetchWithCompletionHandler: selector on your AppDelegate and from there call the checkStatus method from above. You can't, however, control how often this will get called. That's the job of the OS and to some degree the user's preferences. At the end of the processing, be sure to call the completion handler.
You must also set a minimum interval for fetching. In your app's application:didFinishLaunchingWithOptions:, you'll need to add something like this:
-(BOOL)application:(UIApplication*)app didFinishLaunchingWithOptions:
{
[application setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];
return YES;
}
And assuming the code from above is also in the AppDelegate:
- (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler
{
[self checkStatus];
completionHandler(UIBackgroundFetchResultNewData);
}
You'll also have to set a property in your app's Info.plist file. You'll need to add the key UIBackgroundModes with the fetch value.
I need to create a iOS app where the app has to continuously check for the updates from the server(may be every 30 secs). But only when the app is running on the foreground.
I know this will drain the battery, but this will run on a environment where there's no internet. So we can't use push notifications.
Only option I can think of is sending a request to the server every 30 secs or so and get the updates. What is the best way to do this? Using NSTimer and NSURLConnection or any other better approaches?
Also if I use a timer, when the app goes to the background will it pause and will it start running as it comes to the foreground again? Is there a chance that app get killed while its on background?
Thanks
Using NSTimer and NSURLConnection or any other better approaches?
My first thought was also to use NSTimer and NSURLConnection.
Also if I use a timer, when the app goes to the background will it pause and will it start running as it comes to the foreground again?
Yes, it will. It doesn't exactly pause, but based on my testing in the simulator, the effect is similar. Let's say the timer is set to go off at 00:00:00, 00:00:30, 00:00:60, ... and you background the app at 00:00:15 and resume it at 00:00:45. The timer that was supposed to fire at 00:00:30 fires immediately when you resume (at 00:00:45), and the next firing (at 00:00:60) and subsequent firings are back on schedule.
Is there a chance that app get killed while its on background?
Yes, there is. But if you start the timer whenever the app launches, this shouldn't be a problem, right?
Your best bet is to setup a separate object that manages these operations on a background thread. Then in your app delegate, when
- (void)applicationWillResignActive:(UIApplication *)application
is called, have this special object stop all of it's synchronizing and clean up anything it needs to.
Then when:
- (void)applicationDidBecomeActive:(UIApplication *)application
gets called as the app gets active again, signal your object to query / poll on its background thread again.
Your custom object could have an interface like this
#interface PollingObject : NSObject
{
NSTimer* _timer;
NSUinteger _interval;
BOOL _cancel;
BOOL _isPolling;
dispatch_queue_t _pollQueue;
}
- (void)startPolling;
- (void)stopPolling;
#end
The implementation can be something like this:
#implementation PollingObject : NSObject
- (id)init
{
if (self = [super init])
{
_interval = 1; // 1 second interval
_cancel = NO; // default to NO
_isPolling = NO; // default to NO
// init your background queue
_pollQueue = dispatch_queue_create("com.yourconame.yourappname.pollQueue", NULL);
}
return self;
}
- (void)heartbeat
{
if (_cancel)
{
// stop the timer
[_timer invalidate];
_isPolling = NO;
return;
}
// Runs the polling method ONCE on a background queue
dispatch_async(_pollQueue, ^{
[self pollingMethod];
});
}
- (void)pollingMethod
{
// Do actual network polling work here...but only run it once. (don't loop)
}
- (void)startPolling
{
_cancel = NO;
if (_isPolling)
{
NSLog(#"Already polling");
return;
}
// schedule the method heartbeat to run every second
_timer = [NSTimer scheduledTimerWithTimeInterval:_interval target:self selector:#selector(heartbeat) userInfo:nil repeats:YES];
}
- (void)stopPolling
{
// we set the flag here and the next second the heartbeat will stop the timer
_cancel = YES;
}
#end
Look at Rocket real-time networking which looks easy to setup through AFNetworking 2.0.
https://github.com/AFNetworking/AFNetworking/wiki/AFNetworking-2.0-Migration-Guide
See the last part of this wiki. I have not used it but it would be something I would try if I had your requirements.