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
Related
I'm trying to adapt my code from using only WCSessionDelegate callbacks in the foreground to accepting WKWatchConnectivityRefreshBackgroundTask via handleBackgroundTasks: in the background. The documentation states that background tasks may come in asynchronously and that one should not call setTaskCompleted until the WCSession's hasContentPending is NO.
If I put my watch app into the background and transferUserInfo: from an iPhone app, I am able to successfully receive my first WKWatchConnectivityRefreshBackgroundTask. However, hasContentPending is always YES, so I save away the task and simply return from my WCSessionDelegate method. If I transferUserInfo: again, hasContentPending is NO, but there is no WKWatchConnectivityRefreshBackgroundTask associated with this message. That is, subsequent transferUserInfo: do not trigger a call to handleBackgroundTask: –- they're simply handled by the WCSessionDelegate. Even if I immediately setTaskCompleted without checking hasContentPending, subsequent transferUserInfo: are handled by session:didReceiveUserInfo: without me needing to activate a WCSession again.
I'm not sure what to do here. There doesn't seem to be a way to force a WCSession to deactivate, and following the documentation about delaying setTaskCompleted seems to be getting me into trouble with the OS.
I've posted and documented a sample project illustrating my workflow on GitHub, pasting my WKExtensionDelegate code below. Am I making an incorrect choice or interpreting the documentation incorrectly somewhere along the line?
I've looked at the QuickSwitch 2.0 source code (after fixing the Swift 3 bugs, rdar://28503030), and their method simply doesn't seem to work (there's another SO thread about this). I've tried using KVO for WCSession's hasContentPending and activationState, but there's still never any WKWatchConnectivityRefreshBackgroundTask to complete, which makes sense to be given my current explanation of the issue.
#import "ExtensionDelegate.h"
#interface ExtensionDelegate()
#property (nonatomic, strong) WCSession *session;
#property (nonatomic, strong) NSMutableArray<WKWatchConnectivityRefreshBackgroundTask *> *watchConnectivityTasks;
#end
#implementation ExtensionDelegate
#pragma mark - Actions
- (void)handleBackgroundTasks:(NSSet<WKRefreshBackgroundTask *> *)backgroundTasks
{
NSLog(#"Watch app woke up for background task");
for (WKRefreshBackgroundTask *task in backgroundTasks) {
if ([task isKindOfClass:[WKWatchConnectivityRefreshBackgroundTask class]]) {
[self handleBackgroundWatchConnectivityTask:(WKWatchConnectivityRefreshBackgroundTask *)task];
} else {
NSLog(#"Handling an unsupported type of background task");
[task setTaskCompleted];
}
}
}
- (void)handleBackgroundWatchConnectivityTask:(WKWatchConnectivityRefreshBackgroundTask *)task
{
NSLog(#"Handling WatchConnectivity background task");
if (self.watchConnectivityTasks == nil)
self.watchConnectivityTasks = [NSMutableArray new];
[self.watchConnectivityTasks addObject:task];
if (self.session.activationState != WCSessionActivationStateActivated)
[self.session activateSession];
}
#pragma mark - Properties
- (WCSession *)session
{
NSAssert([WCSession isSupported], #"WatchConnectivity is not supported");
if (_session != nil)
return (_session);
_session = [WCSession defaultSession];
_session.delegate = self;
return (_session);
}
#pragma mark - WCSessionDelegate
- (void)session:(WCSession *)session activationDidCompleteWithState:(WCSessionActivationState)activationState error:(NSError *)error
{
switch(activationState) {
case WCSessionActivationStateActivated:
NSLog(#"WatchConnectivity session activation changed to \"activated\"");
break;
case WCSessionActivationStateInactive:
NSLog(#"WatchConnectivity session activation changed to \"inactive\"");
break;
case WCSessionActivationStateNotActivated:
NSLog(#"WatchConnectivity session activation changed to \"NOT activated\"");
break;
}
}
- (void)sessionWatchStateDidChange:(WCSession *)session
{
switch(session.activationState) {
case WCSessionActivationStateActivated:
NSLog(#"WatchConnectivity session activation changed to \"activated\"");
break;
case WCSessionActivationStateInactive:
NSLog(#"WatchConnectivity session activation changed to \"inactive\"");
break;
case WCSessionActivationStateNotActivated:
NSLog(#"WatchConnectivity session activation changed to \"NOT activated\"");
break;
}
}
- (void)session:(WCSession *)session didReceiveUserInfo:(NSDictionary<NSString *, id> *)userInfo
{
/*
* NOTE:
* Even if this method only sets the task to be completed, the default
* WatchConnectivity session delegate still picks up the message
* without another call to handleBackgroundTasks:
*/
NSLog(#"Received message with counter value = %#", userInfo[#"counter"]);
if (session.hasContentPending) {
NSLog(#"Task not completed. More content pending...");
} else {
NSLog(#"No pending content. Marking all tasks (%ld tasks) as complete.", (unsigned long)self.watchConnectivityTasks.count);
for (WKWatchConnectivityRefreshBackgroundTask *task in self.watchConnectivityTasks)
[task setTaskCompleted];
[self.watchConnectivityTasks removeAllObjects];
}
}
#end
From your description and my understanding it sounds like this is working correctly.
The way this was explained to me is that the new handleBackgroundTasks: on watchOS is intended to be a way for:
the system to communicate to the WatchKit extension why it is being launched/resumed in the background, and
a way for the WatchKit extension to let the system know when it has completed the work it wants to do and can therefore be terminated/suspended again.
This means that whenever an incoming WatchConnectivity payload is received on the Watch and your WatchKit extension is terminated or suspended you should expect one handleBackgroundTasks: callback letting you know why you are running in the background. This means you could receive 1 WKWatchConnectivityRefreshBackgroundTask but several WatchConnectivity callbacks (files, userInfos, applicationContext). The hasContentPending lets you know when your WCSession has delivered all of the initial, pending content (files, userInfos, applicationContext). At that point, you should call the setTaskCompleted on the WKWatchConnectivityRefreshBackgroundTask object.
Then you can expect that your WatchKit extension will soon thereafter be suspended or terminated unless you have received other handleBackgroundTasks: callbacks and therefore have other WK background task objects to complete.
I have found that when attaching to the processes with a debugger that OSs might not suspended them like they normally would, so it'd suggest inspecting the behavior here using logging if you want to be sure to avoid any of those kind of issues.
I have registered for Calendar Change Notifications using the following:
- (void) registerForLocalCalendarChanges
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(localCalendarStoreChanged) name:EKEventStoreChangedNotification object:self.store ];
}
This should call the following when a change is made to the local calendar:
- (void) localCalendarStoreChanged
{
// This gets called when an event in store changes
// you have to go through the calendar to look for changes
// launch this in a thread of its own!
ashsysCalendarEventReporter *eventReport = [ashsysCalendarEventReporter new];
NSLog(#"Local Calendar Store Changed");
[NSThread detachNewThreadSelector:#selector(getCalendarEvents) toTarget:eventReport withObject:nil];
}
BUT...when I start the app, then send it to the background so I can change a calendar entry, nothing happens when I change the calendar entry. It DOES fire when I return to the app. But, of course that is not the objective.
store is defined in the header file with:
#property (strong,nonatomic) EKEventStore *store;
Update...forgot to show the stuff I have in the background fetch.
This is in didFinishLaunchingWithOptions
[application setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];
This is in the app delegate:
- (void) application:(UIApplication*) application performFetchWithCompletionHandler:(void (^) (UIBackgroundFetchResult))completionHandler
{
// UIBackgroundTaskIdentifier uploadCalInfo = [application beginBackgroundTaskWithExpirationHandler:nil];
NSLog(#"A fetch got called");
// ashsysCalendarEventReporter *eventReport = [ashsysCalendarEventReporter new];
// [eventReport getCalendarEvents];
// // [NSThread detachNewThreadSelector:#selector(getCalendarEvents) toTarget:eventReport withObject:nil];
// [application endBackgroundTask:uploadCalInfo];
completionHandler(UIBackgroundFetchResultNewData);
The performFetch gets called at what seem like random times some not at all related to the calendar. Is there a way to find out what is firing the background fetch? Is it always the calendar? The actual execution is commented out -- is it correct?
What am I missing?
I've been trying to find this answer out myself and I don't think there's an actual way to accomplish this. As per Apple's documentation, we're not allowed access to system resources while in a background state:
Stop using shared system resources before being suspended. Apps that interact with shared system resources such as the Address Book or calendar databases should stop using those resources before being suspended. Priority for such resources always goes to the foreground app. When your app is suspended, if it is found to be using a shared resource, the app is killed.
I'm still looking for a better answer/work around but would love to hear this from anyone else.
I have a situation in which it can happen, that the last strong reference to an observer is removed while the observer processes an incoming notification.
That leads to the observer being deallocated immediately. I would normally expect, that a currently running method can finish before an object is deallocated. And this is what happens during normal message dispatch.
A simplified version of the code:
TKLAppDelegate.h:
#import <UIKit/UIKit.h>
#import "TKLNotificationObserver.h"
#interface TKLAppDelegate : UIResponder <UIApplicationDelegate>
#property (strong, nonatomic) TKLNotificationObserver *observer;
#end
TKLAppDelegate.m:
#import "TKLAppDelegate.h"
#implementation TKLAppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Create an observer and hold a strong reference to it in a property
self.observer = [[TKLNotificationObserver alloc] init];
// During the processing of this notification the observer will remove the only strong reference
// to it and will immediatly be dealloced, before ending processing.
[[NSNotificationCenter defaultCenter] postNotificationName:#"NotificationName" object:nil];
// Create an observer and hold a strong reference to it in a property
self.observer = [[TKLNotificationObserver alloc] init];
// During the manual calling of the same method the observer will not be dealloced, because ARC still
// holds a strong reference to the message reciever.
[self.observer notificationRecieved:nil];
return YES;
}
#end
TKLNotificationObserver.m:
#import "TKLNotificationObserver.h"
#import "TKLAppDelegate.h"
#implementation TKLNotificationObserver
- (id)init {
if (self = [super init]) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(notificationRecieved:) name:#"NotificationName" object:nil];
}
return self;
}
- (void)notificationRecieved:(NSNotification *)notification {
[self doRemoveTheOnlyStrongReferenceOfThisObserver];
NSLog(#"returing from notification Observer");
}
- (void)doRemoveTheOnlyStrongReferenceOfThisObserver {
TKLAppDelegate * delegate = [[UIApplication sharedApplication] delegate];
delegate.observer = nil;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"NotificationName" object:nil];
NSLog(#"dealloc was called");
}
#end
Using the App Delegate in this way is no good style and only done for demonstration purposes, the real code does not involve the app delegate.
The output is:
dealloc was called
returing from notification Observer
returing from notification Observer
dealloc was called
That is in the first case dealloc is called before the notification processing finished. In the second case it behaves as I expected.
If I keep a strong reference to self inside notificationReceived the dealloc only happens after the processing. My expectation was, that ARC, the runtime or whoever else keeps this strong reference for me.
What is wrong with my code?
Or is something wrong with my expectation?
Is there any Apple- or Clang-provided documentation on this?
My expectation was, that ARC, the runtime or whoever else keeps this
strong reference for me.
That is not the case, as documented in the Clang/ARC documentation:
The self parameter variable of an Objective-C method is never actually
retained by the implementation. It is undefined behavior, or at least
dangerous, to cause an object to be deallocated during a message send
to that object.
Therefore, if calling doRemoveTheOnlyStrongReferenceOfThisObserver
can have the side-effect of releasing self, you would have to use
an temporary strong reference to avoid deallocation:
- (void)notificationRecieved:(NSNotification *)notification {
typeof(self) myself = self;
[self doRemoveTheOnlyStrongReferenceOfThisObserver];
NSLog(#"returing from notification Observer");
}
A better solution would probably to avoid this side-effect.
the first dealloc probably happens as you set the observer property of the appDelegate twice and therefore the first instance is dealloced as soon as you set it the second time
First, thanks to everyone that takes the time to answer questions. I have gotten so many quick answers to problems over the years from StackOverflow.
I'm new to Object C and iOS programming, but starting with what I think should be a super simple app. It receives a push notification (which works fine) and redirects to a webpage when it has figured out its appid.
The problem is that the while I can get my UIWebView to loadRequest in the viewDidLoad, the same code will not execute in another function.
Here's the code:
AppDelegate.m
// UFAppDelegate.m
#import "UFAppDelegate.h"
#import "UFViewController.h"
#implementation UFAppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Add registration for remote notifications
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:
(UIRemoteNotificationType)(UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound)];
return YES;
}
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)devToken {
// ....
NSString *urlString = #"http://www.google.com";
NSURL *url = [NSURL URLWithString:urlString];
NSLog(#"Navigating to URL: %#", url);
UFViewController *theview = [[UFViewController alloc] init];
[theview handleOpenURL:url];
}
#end
ViewController.h:
// UFViewController.h
#import <UIKit/UIKit.h>
#interface UFViewController : UIViewController
- (void)handleOpenURL:(NSURL *)url;
#end
ViewController.m:
// UFViewController.m
#import "UFViewController.h"
#interface UFViewController ()
#property (weak, nonatomic) IBOutlet UIWebView *UFHWebView;
- (void)handleOpenURL:(NSURL *)url;
#end
#implementation UFViewController
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"UFViewController.viewDidLoad started");
// This block that assigns the url and loads it works perfectly here... but not down below.
NSString *urlString = #"http://search.yahoo.com/";
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *requestObj = [NSURLRequest requestWithURL:url];
[_UFHWebView loadRequest:requestObj];
NSLog(#"UFViewController.viewDidLoad completed");
}
- (void)handleOpenURL:(NSURL *)url
{
NSLog(#"UFViewController.handleOpenURL started");
NSLog(#"url = %#",url);
// The below loadRequest does not load!
NSURLRequest *requestObj = [NSURLRequest requestWithURL:url];
[_UFHWebView loadRequest:requestObj];
NSLog(#"UFViewController.handleOpenURL completed");
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
NSLog(#"Error - %#", error);
}
- (BOOL) webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
return YES;
}
#end
When run as coded here, the yahoo page shows from the loadRequest in the didload, but the handleOpenURL one still does not fire. If I comment out the loadRequest out of viewDidLoad, a blank page shows up and the handleOpenURL still does not fire.
Here's the debug sequence. viewDidLoad fires and completes before receiving the AppID and manually firing handleOpenURL:
2013-12-12 15:28:32.606 UF[5896:60b] UFViewController.viewDidLoad started
2013-12-12 15:28:32.608 UF[5896:60b] UFViewController.viewDidLoad completed
2013-12-12 15:28:32.765 UF[5896:60b] Navigating to URL: http://www.google.com
2013-12-12 15:28:32.769 UF[5896:60b] UFViewController.handleOpenURL started
2013-12-12 15:28:32.773 UF[5896:60b] url = http://www.google.com
2013-12-12 15:28:32.775 UF[5896:60b] UFViewController.handleOpenURL completed
Any help appreciated!
Are you sure you have just one instance of UFViewController here?
-didRegisterForRemoteNotificationsWithDeviceToken initializes a new web view controller in a local variable, but I can't see how it's getting shown to the user - you aren't doing anything with the vc once it's created.
Here's what I think might be happening:
App starts. You have an instance of UFViewController in a storyboard. It loads, its -viewDidLoad is called, the log statements print and the webview loads correctly.
A little later, the -didRegisterForRemoteNotificationsWithDeviceToken: method is called. This instantiates a UFViewController, and calls the -handleOpenUrl method on it. The -handleOpenURL log methods fire. However, this controller has not been loaded into the view hierarchy, so its view is never instantiated, and its -viewDidLoad is never called.
This means that the webview that the -loadRequest: is being called on is nil. In Objective-C, it is valid to send a message to nil- unlike most other languages, no error or exception will be thrown. Instead, absolutely nothing happens, hence no web load.
The UFViewController in -handleOpenUrl is only referred to by a local variable within the method, so when the method completes, the last strong reference to it vanishes and it is released.
To check this is correct:
-NSLog(#"%#",[self description]); in your vc methods - this will log the address of the objects and tell you whether they are the same.
-NSLog the web controller's view, to check whether it's nil in handleOpenURL
To solve your problem, I suggest using an NSNotification posted by the AppDelegate and listened for by the UFViewController that is fully instantiated. So in the vc you'd do:
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(handleRegistration:) name:#"HandleRegistrationNotification" object:nil];
and then implement the handleRegistration method:
-(void)handleRegistration:(NSNotification*)notification
{
//your handleOpenUrl code
}
and also
-(void)dealloc
{
[[NSNotificationCenter defaultCenter]removeObserver:self name:#"HandleRegistrationNotification" object:nil];
}
(this bit is important because otherwise you may get a crash when the vc is dealloc'd)
Then, in the -didRegisterForRemoteNotifications: method:
[[NSNotificationCenter defaultCenter]postNotificationName:#"HandleRegistrationNotification" object:nil];
If you need to pass any information through the notification (eg urls) you can use the object parameter - this is accessible in the method called on the vc via the object property of the NSNotification parameter.
Full docs on notifications in Objective-C can be found here.
Some minor points - it's generally a bad idea to call your view controllers things like theView rather than theViewController since other people (or you in 6 months time) will end up thinking they're UIView s. Also the webview property should not start with a capital letter- that's generally only used for class names.
This should be called, "How to call a storyboard viewcontroller method from the appdelegate". And the answer is:
in AppDelegate.m:
#import "UFViewController.h"
...
UFViewController *rootViewController = (UFViewController*)self.window.rootViewController;
[rootViewController handleOpenURL:url];
In my code snippets above, this means replacing these lines
UFViewController *theview = [[UFViewController alloc] init];
[theview handleOpenURL:url];
with the ones above. The difference is my original code instantiates a new UFViewController object into a local variable, while the correct code just gets a handle to the view controller already created in the storyboard and calls its method.
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);
}