I am currently developing an app that will need to return to another view after running in the background for more than five minutes. In order to do this, I will have to have a timer running in the background after the the Home button has been pressed or in case of an interruptions such as an SMS or a telephone call, then, after five minutes the app will need to go to another view. I know that the applicationDidBecomeActive method will have to be used, but how? I also know that a view can be refreshed in applicationDidBecomeActive but how is that done? ( I am not using storyboards.)
Actually, you should do this with the applicationDidEnterBackground applicationWillEnterForeground delegate methods of UIAppDelegate or by registering to the appropriate system notifications (didBecomeActive is called on other occasions too, such as when a UIAlertView is dismissed from screen).
This should be something in the lines of (may include syntax problems, I'm textbox-coding here):
In the viewDidLoad method of your view controller, register to the notifications:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(willEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(didEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];
Implement the willEnterForeground: and didEnterBackground: methods. In willEnterForeground: sample the current time using CACurrentMediaTime() or [NSDate date]. In didEnterBackground: sample the time again and calculate the time difference. Since this method is implemented inside the view controller, you can manipulate the subviews of your self.view as you wish.
Do not forget to remove the observers on your dealloc method (viewDidUnload is deprecated since iOS 6.0, so beware):
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil]
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil]
Here's how you can do it. I just made a test app and I confirm that it works beautifully. Code:
#import "AppDelegate.h"
#import "ViewController.h"
#import "theView.h"
NSTimer *theTimer;
UIViewController *theViewController;
BOOL theTimerFired = NO;
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.viewController = [[ViewController alloc] initWithNibName:#"ViewController" bundle:nil];
self.window.rootViewController = self.viewController;
[self.window makeKeyAndVisible];
return YES;
}
- (void)applicationWillResignActive:(UIApplication *)application
{
// Set a 5 minute timer (300 seconds)
theTimer = [NSTimer scheduledTimerWithTimeInterval:300.0 target:self selector:#selector(presentVC) userInfo:nil repeats:NO];
}
- (void)presentVC
{
// Set a boolean to indicate the timer did fire
theTimerFired = YES;
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
// Check to see if the timer did fire using the previous boolean we created
if (theTimerFired == YES)
{
theViewController = [[UIViewController alloc]initWithNibName:#"theView" bundle:nil];
[self.viewController presentViewController:theViewController animated:YES completion:NULL];
[theTimer invalidate];
}
}
#end
Related
I have two classes. A and B for example.
I have created an observer in A like this
- (void)viewDidLoad
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(somethingHappens:) name:#"notificationName" object:nil];
}
-(void)somethingHappens:(NSNotification*)notification
{
NavigationController *navigationController = [self.storyboard instantiateViewControllerWithIdentifier:#"contentViewController"];
UIViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"loyaltyVC"];
navigationController.viewControllers = #[vc];
}
I want to call this observe from page B. I'm using like this
[[NSNotificationCenter defaultCenter] postNotificationName:#"notificationName" object:self];
Do you have any suggestion how we can do this ?
To be precise this are not push notifications. The following are just normal in-app-notifications.
Your -(void)viewDidLoad should contain a call to its super method at some point.
//in A
#implementation AThingy {
id<NSObject> observeKeeper;
}
- (void)viewDidLoad {
[super viewDidLoad];
observeKeeper = [[NSNotificationCenter defaultCenter] addObserverForName:#"notificationName" object:nil queue:[NSOperationQueue currentQueue] usingBlock:^(NSNotification * _Nonnull note) {
UINavigationController *nav = [self.storyboard instantiateViewControllerWithIdentifier:#"contentViewController"];
UIViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"loyaltyVC"];
nav.viewControllers = #[vc];
}];
}
// not mandatory but to explain it complete
-(void)dealloc {
// could be placed in some other method also.. like -viewDidDisappear:
[[NSNotificationCenter defaultCenter] removeObserver:observeKeeper];
}
#end
//in B
#implementation BThingy
-(void)postNotify {
[[NSNotificationCenter defaultCenter] postNotificationName:#"notificationName" object:nil];
}
#end
But keep in mind that NotificationCenter is working relative to its observing Objects and so it matters if you watch and post in general on (nil) or on some specific object. Why is that? Because you could decide to instantiate two objects from the same class that observe notifications and still specifically observe each notification meant for them or both.
Have you tried to print a log in method somethingHappens:?
Is your method -(void)didLoad ever been called?
In my app, UITabBarController is an initial ViewController build in storyBoard. I would like to observe data updating in my whole app. So I add an observer in appDelegate...
AppDelegate.m
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(shipCountBadge:) name:kShipCountBadge object:nil];
- (void)shipCountBadge:(NSNotification *)notification{
Groups *vc = [[Groups alloc] init];
[vc addBadgeCount:notification.object];
}
Group.m
- (void)addBadgeCount:(NSNumber *)count{
NSLog(#"d",[count intValue]);
[[self.tabBarController.tabBar.items objectAtIndex:2] setBadgeValue:[NSString stringWithFormat:#"%d",[count intValue]]];
}
Its doesn't change the UI at all. I would like to know how to update UI in AppDelegate actually? Anyone has an idea :)?
Here's the question:
Can one View Controller add another View Controller as an Observer to the defaultCenter before the second view has been loaded?
I have a model class that creates a NSURLSession, grabs some data, builds an array, and sends notifications that it's done (along with a pointer to the array).
My app loads with a Map View that instantiates the model, calls the method to create the array, listens for the notification, and drops pins using the array.
I have a Table View tab that I want to load using the array built by the map.
Can my Map View add my Table View Controller as an observer before the Table View is loaded?
Something like:
[[NSNotificationCenter defaultCenter] addObserver: TableViewController ...
Thanks for any insight. I'm figuring this out as I go.
-----------------EDIT--------------------
viewDidLoad from MapViewController:
- (void)viewDidLoad
{
[super viewDidLoad];
_mapView.delegate = self;
_model = [[WikiModel alloc] init];
_lastArticleUpdateLocation = [[CLLocation alloc] initWithLatitude:0 longitude:0];
_lastUpdateUserLocation = [[CLLocation alloc] initWithLatitude:0 longitude:0];
// Listen for WikiModel to release updates.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(reloadData:)
name:#"Array Complete"
object:_model];
//table view listener attempt ...
UITableViewController *tvc = [self.storyboard instantiateViewControllerWithIdentifier:#"tableViewController"];
[[NSNotificationCenter defaultCenter] addObserver:tvc
selector: #selector(updateDataSource:)
name:#"Array Complete"
object:nil];
[self.navigationController pushViewController:tvc animated:YES];
}
From the TableViewController:
- (void)viewDidLoad
{
[super viewDidLoad];
// Listen for WikiModel to release updates.
/*
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(updateDataSource:)
name:#"Array Complete"
object:nil];*/
}
-(void)updateDataSource:(NSNotification *)notification
{
_wikiEntries = [[notification userInfo] objectForKey:#"wikiEntryArray"];
[self.tableView reloadData];
NSLog(#"************received***********");
}
This is possible as long as you pass the pointer to your view controller as the observer. Here's an example:
//Go to the detail view
VC2ViewController *dvc = [self.storyboard instantiateViewControllerWithIdentifier:#"VC2ViewController"]; //ID specified in the storyboard
[[NSNotificationCenter defaultCenter] addObserver:dvc selector:#selector(notificationReceived:) name:#"MyNotification" object:nil];
[self.navigationController pushViewController:dvc animated:YES];
Then, you can post your notification as usual:
[[NSNotificationCenter defaultCenter] postNotificationName:#"MyNotification" object:nil];
I just tested the above code and it worked fine for me, the following function was called in my detail view controller:
-(void)notificationReceived:(NSNotification *)notification {
NSLog(#"RECEIVED");
}
When the App enters the background, it logs the person out of the application (per the specifications).
I want to transition back to the first view controller. This is not a navigation or a tab bar controller (although it does transition into those after the first scene).
I tried this in the AppDelegate
- (void)applicationDidEnterBackground:(UIApplication *)application
{
LoginRegisterViewController *controller = [[LoginRegisterViewController alloc] init];
[self.window setRootViewController:controller];
}
but it just transitions me to a black screen.
Any suggestions?
Rather than performing the transition on applicationDidEnterBackground: do it in applicationWillEnterForeground:.
I think you want something like a locked screen whick will show when enter foreground again. Add a sub UIWindow may be better.
Try This code
(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(applicationDidTimeout:)name:kApplicationDidTimeoutNotification object:nil];
return YES;
}
(void) applicationDidTimeout:(NSNotification *) notif {
UINavigationController *navigationController = (UINavigationController *)self.window.rootViewController;
[navigationController popToRootViewControllerAnimated:YES];
}
(void)applicationWillResignActive:(UIApplication *)application
{
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
[[NSNotificationCenter defaultCenter] postNotificationName:kApplicationDidTimeoutNotification object:nil];
}
Sweet & simple coding......
This was the only one that worked for me,
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
LoginRegisterViewController *myVC = (LoginRegisterViewController *)[storyboard instantiateInitialViewController];
[self.window setRootViewController:myVC];
in the foreground method.
I'm working with UILocalNotification and I would like to notify one of my controller that the notification has been received even if the app has been terminated.
In my appDelegate I implemented this function:
-(void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
{
if ([application applicationState] == UIApplicationStateInactive) {
[[NSNotificationCenter defaultCenter] postNotificationName:#"localNotificationReceived" object:notification.userInfo];
}
}
In my UIViewController I implemented the observer on the viewDidLoad method
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(didlocalNotificationReceived:) name:#"localNotificationReceived" object:nil];
}
It works perfectly when the app run in background. Since the viewDidLoad method has already been called and the Observer is waiting..
The issue is when I kill the app. Then the observer of my controller is gone and the didlocalNotificationReceived method is never called.
I think that it's because when I receive the localNotification and run the app again. The didReceiveLocalNotification: method is called before the viewDidLoad of my UIViewController. Then the observer is created after the PostNotificationName then the observer receives nothing.
I would like to know if there is some best practices or pattern to handle this kind of issue.
I know that the didFinishLaunchingWithOptions method is called before didlocalNotificationReceived so there is probably something to do there.
UPDATE :
I also discovered that when is app is terminated. Once you tap the notification, It opens the app, call the function didFinishLaunchingWithOptions but never call didReceiveLocalNotification. So I think that I'm gonna handle both cases differently.
Ok I found the answer.
I actually, initialize manually my storyboard, and be cautious that I initialize my main view before posting the NSNotification
My didFinishLaunchingWithOptions: method in my appDelegate looks like that:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:[NSBundle mainBundle]];
UIViewController *vc =[storyboard instantiateInitialViewController]; //call the initWithCoder: method of my controller
if (launchOptions[UIApplicationLaunchOptionsLocalNotificationKey]) {
UILocalNotification *localNotification = launchOptions[UIApplicationLaunchOptionsLocalNotificationKey];
[[NSNotificationCenter defaultCenter] postNotificationName:#"localNotificationReceived" object:localNotification.userInfo];
}
self.window.rootViewController = vc;
[self.window makeKeyAndVisible];
return YES;
}
Then in my UIViewController I create the NSNotification observer in the initWithCoder: method instead of in viewDidLoad:
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(didlocalNotificationReceived:) name:#"localNotificationReceived" object:nil];
}
return self;
}
- (void)didlocalNotificationReceived:(NSNotification *)notification
{
//Execute whatever method when received local notification
}
And when the app is not killed I still use the didReceiveLocalNotification: method:
-(void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
{
if ([application applicationState] == UIApplicationStateInactive) {
[[NSNotificationCenter defaultCenter] postNotificationName:#"localNotificationReceived" object:notification.userInfo];
}
}
I'm not sure if it's the best practice. But it works well !
Hope it'll help :)