I know similar questions are asked but none of them helped.
My Observation
There are two cases: 1) When app is running and in the foreground, visible to the user 2) User presses home button and app moves to the background, not visible to the user. In case 1) when a phone call comes and end I get all the events with no problem. In case 2) I get no events, but when user opens the app, then I get all the events that has happened when app was in the background.
My Question
How can I get my code working even when app is in the background? Or, how can I move my app from background to foreground when those events happen?
What I've tried
I've tried enabling background fetch at Background Modes at Info.plist with an implementation of -(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler. It had no effect.
Below is my code:
AppDelegate.m:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// some other code above
self.callCenter = [[CTCallCenter alloc] init];
[self.callCenter setCallEventHandler:^(CTCall *call)
{
UINavigationController *nc = (UINavigationController *) [[(AppDelegate *) [[UIApplication sharedApplication] delegate] window] rootViewController];
MainViewController *vc = nc.viewControllers[0];
[vc setCallState:call.callState];
}
}
MainViewController.m
-(void)setCallState:(NSString *)callStateString
{
if ([callStateString isEqualToString:CTCallStateConnected])
{
NSLog(#"call connected!");
} else if ([callStateString isEqualToString:CTCallStateDialing])
{
NSLog(#"call dialing!");
} else if ([callStateString isEqualToString:CTCallStateDisconnected])
{
NSLog(#"call disconnected!");
} else if ([callStateString isEqualToString:CTCallStateIncoming])
{
NSLog(#"call incoming!");
} else
{
NSLog(#"unknown call state %#", callStateString);
}
}
Related
I want to pull some data from server in every 30 min interval and set local notification to remind the user and i implemented below code to perform this operation.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[application setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];
return YES;
}
-(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{
NSString *value = [[NSUserDefaults standardUserDefaults]objectForKey:#"bgFetch"];
if ([Utilities isInternetConnectionAvailable]) {
if ([self.window.rootViewController isKindOfClass:[HomeViewController class]]){
HomeViewController *homeController = (HomeViewController*)self.window.rootViewController;
[homeController fetchDatawithCompletionHandler:^(UserData *userData, NSString *errorResponse) {
if (userData) {
if (![homeController isNotificationAvailvableForTheData:userData]) {
[homeController scheduleLocalNotificationForUserData:userData];
completionHandler(UIBackgroundFetchResultNewData);
}
}else{
completionHandler(UIBackgroundFetchResultFailed);
}
}];
}else{
completionHandler(UIBackgroundFetchResultNoData);
}
}else{
completionHandler(UIBackgroundFetchResultFailed);
}
}
I also enabled background fetch in capability and added the key "Required background modes" in plist. When i checked after few hours, no local notification is set. Where am i doing wrong ?
I was facing the same issue. Whenever I simulated a background fetch via XCode it worked, whether with the emulator or a real device. If I unplugged the phone and just used it as I usually do. I never got a notification, respectively my code doesn't get executed. The solution is very simple and I think the difference to "simulate background fetch" is that your app is in a different stage then it is in your daily routine. To make it work, simply dispatch your code on a background thread. I just wrapped my code inside:
DispatchQueue.global(qos: .background).async
and it worked.
I'm using these code to
1) overlay an image when the app is inactive (double tap the home button twice).
2) Remove the image when the app is active (reopen the app)
In appdelegate.h file:
#property (nonatomic, strong) UIImageView *splashScreenImageView;
In appdelegate.m file:
- (void)applicationWillResignActive:(UIApplication *)application
{
UIImage *splashScreenImage = [UIImage imageNamed:#"BackgroundScreenCaching"];
_splashScreenImageView = [[UIImageView alloc] initWithImage:splashScreenImage];
[self.window addSubview:_splashScreenImageView];
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
if(_splashScreenImageView != nil) {
[_splashScreenImageView removeFromSuperview];
_splashScreenImageView = nil;
}
}
Problem:
However, SOMETIME when pressing the home button twice, the iOS still caches the app screen with sensitive information instead of the overlay image in iOS 11. Tested no issue in iOS10.
Updated
Issue still persist after changing to this:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
BOOL result = [super application:application didFinishLaunchingWithOptions:launchOptions];
...
UIImage *splashScreenImage = [UIImage imageNamed:#"BackgroundScreenCaching"];
_splashScreenImageView = [[UIImageView alloc] initWithImage:splashScreenImage];
[_splashScreenImageView setFrame:self.window.bounds];
return result;
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
if(_splashScreenImageView != nil) {
[_splashScreenImageView removeFromSuperview];
}
}
- (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.
[self.window addSubview:_splashScreenImageView];
}
I found a tricky way and it works with iOS 11 or iOS 10.
In Appdelegate.m file:
#implementation AppDelegate {
UIImageView *splashScreenImageView;
UIViewController *viewController;
}
Add this code in didFinishLaunchingWithOptions to set image and update frame
splashScreenImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height)];
splashScreenImageView.image = [UIImage imageNamed:#"BackgroundScreenCaching"];
Implement this method for getting topmost view controller
- (UIViewController *)topViewController{
return [self topViewController:[UIApplicationsharedApplication].keyWindow.rootViewController];
}
- (UIViewController *)topViewController:(UIViewController *)rootViewController
{
if (rootViewController.presentedViewController == nil) {
return rootViewController;
}
if ([rootViewController.presentedViewController isKindOfClass:[UINavigationController class]]) {
UINavigationController *navigationController = (UINavigationController *)rootViewController.presentedViewController;
UIViewController *lastViewController = [[navigationController viewControllers] lastObject];
return [self topViewController:lastViewController];
}
UIViewController *presentedViewController = (UIViewController *)rootViewController.presentedViewController;
return [self topViewController:presentedViewController];
}
Now in applicationWillResignActive method first get the top viewController and set splashScreenImageView on that view as subview
- (void)applicationWillResignActive:(UIApplication *)application {
viewController = [self topViewController];
[viewController.view addSubview:splashScreenImageView];
}
Finally in applicationDidBecomeActive when app open first remove the overlay image and open app
- (void)applicationDidBecomeActive:(UIApplication *)application {
[splashScreenImageView removeFromSuperview];
}
This tricks will work.
Your code should work fine ,
May be your app didn't get time to alloc your imageView on time when applicationWillResignActive called
and you are re-creating object every time . with [UIImageView alloc] initWithImage
So my suggestion is put this code in didFinishLaunching
UIImage *splashScreenImage = [UIImage imageNamed:#"BackgroundScreenCaching"];
_splashScreenImageView = [[UIImageView alloc] initWithImage:splashScreenImage];
and just add subview on UIWindow and remove it when applicationDidBecomeActive
Also bring _splashScreenImageView to front !!
Hope it may solve your problem
Good Luck
UPDATE
to add in main queue
dispatch_async(dispatch_get_main_queue(), ^{
[self.window addSubview:_splashScreenImageView];
[self.window bringSubviewToFront:_splashScreenImageView];
});
to remove in main queue
dispatch_async(dispatch_get_main_queue(), ^{
[_splashScreenImageView removeFromSuperview];
});
UPDATE 2
according to https://developer.apple.com/library/content/qa/qa1838/_index.html
and
https://developer.apple.com/library/content/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/StrategiesforHandlingAppStateTransitions/StrategiesforHandlingAppStateTransitions.html
Prepare for the App Snapshot
Shortly after an app delegate’s applicationDidEnterBackground: method returns, the system takes a snapshot of the app’s windows. Similarly, when an app is woken up to perform background tasks, the system may take a new snapshot to reflect any relevant changes. For example, when an app is woken to process downloaded items, the system takes a new snapshot so that can reflect any changes caused by the incorporation of the items. The system uses these snapshot images in the multitasking UI to show the state of your app.
If you make changes to your views upon entering the background, you can call the snapshotViewAfterScreenUpdates: method of your main view to force those changes to be rendered. Calling the setNeedsDisplay method on a view is ineffective for snapshots because the snapshot is taken before the next drawing cycle, thus preventing any changes from being rendered. Calling the snapshotViewAfterScreenUpdates: method with a value of YES forces an immediate update to the underlying buffers that the snapshot machinery uses.
Try to present temp view controller applicationDidEnterBackground as done in demo of Apple
I fix your bug like this:
#import "AppDelegate.h"
#interface AppDelegate () {
UIImageView *test ;
}
#end
#implementation AppDelegate
- (void)applicationDidEnterBackground:(UIApplication *)application {
UIImage *splashScreenImage = [UIImage imageNamed:#"ic_target_black"];
test = [[UIImageView alloc] initWithImage:splashScreenImage];
[self.window addSubview:test];
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
if (test) {
[test removeFromSuperview];
}
}
#end
Do this two simple steps to set your black image when double click the app and it is in recent apps list. And remove the black image when the application becomes active.
#interface AppDelegate (){
UIImageView *test ;
}
- (void)applicationWillResignActive:(UIApplication *)application {
UIImage *splashScreenImage = [UIImage imageNamed:#"ss.png"];
test = [[UIImageView alloc] initWithImage:splashScreenImage];
[self.window addSubview:test];
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
if (test) {
[test removeFromSuperview];
}
}
My push notification works properly in Normal case but i have problem with following case:
1) when my app remove from background and get notification & tap on app icon then i want to push view controller and display payload data in that view controller.
2 ) when my app in background and get notification & tap on app icon then i want to push view controller and display payload data in that view controller.
following is my userInfo
{
aps = {
alert = "Call from rohan panchal";
appointmentId = 220;
badge = 0;
"call_token" = "T1==cGFydG5lcl9pZD00NTI1ODY1MiZzaWc9MzM1MmM0M2E2MjkwN2JiYWMzNjgyNjk0MjFlZWMyNWEzNTZmZmM3MjpzZXNzaW9uX2lkPTJfTVg0ME5USTFPRFkxTW41LU1UUTNNREl3TVRBd01qVXdOWDV3WXpCRFMyWTRlR2xhUWpGdU1YbFpNamhvV0hoNFVHTi1VSDQmY3JlYXRlX3RpbWU9MTQ3MDIwMTAwMiZyb2xlPXB1Ymxpc2hlciZub25jZT0xNDcwMjAxMDAyLjUyMDM0NDAzNjQzMjMmZXhwaXJlX3RpbWU9MTQ3MDgwNTgwMg==";
doctorId = 238;
"doctor_country" = US;
"doctor_name" = "John smith";
patientId = 239;
"patient_country" = US;
"patient_name" = "Lottry patel";
sessionId = "2_MX40NTI1ODY1Mn5-MTQ3MDIwMTAwMjUwNX5wYzBDS2Y4eGlaQjFuMXlZMjhoWHh4UGN-UH4";
sound = default;
};
}
following my code.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// when i remove app from background & click on notification then following code run.
NSDictionary *notificationPayload = launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey];
if(notificationPayload)
{
NSLog(#"%#",notificationPayload);
WebViewController *DashBoard = [[WebViewController alloc]initWithNibName:#"WebViewController" bundle:nil];
self.navcntrl=[[UINavigationController alloc]initWithRootViewController:DashBoard];
}
else
{
DoctorMenuViewController *DoctorVC = [[DoctorMenuViewController alloc]initWithNibName:#"DoctorMenuViewController" bundle:nil];
self.navcntrl=[[UINavigationController alloc]initWithRootViewController:DoctorVC];
}
}
When I got notification then following method called.
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
NSLog(#"%#",userInfo);
WebViewController *DashBoard = [[WebViewController alloc]initWithNibName:#"WebViewController" bundle:nil];
[self.navcntrl pushViewController:DashBoard animated:YES];
}
Please help.Any help appreciated.
The push notification payload consists of:
alert - the alert string and actions
badge
sound
content-available
The key content-available is a new feature, and it is this key that makes silent push possible.
To enable, you also have to add remote-notifcation as your app UIBackgroundModes as described here.
This is what happens when content-available is in the payload:
If app is Suspended, the system will bring it into Background
If app was killed by user, nothing happens and app remains in Not Running
Read about app state changes.
A potential is pitfall:
You enable with content-available=1. But, it is WRONG to disable with content-available=0. To disable, you have to REMOVE the key in the payload.
plz use this
-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
if(application.applicationState == UIApplicationStateInactive)
{
NSLog(#"Inactive");
//do your things when you click on notification
}
else if (application.applicationState == UIApplicationStateBackground)
{
NSLog(#"Background");
}
else if (application.applicationState == UIApplicationStateActive)
{
NSLog(#"Active");
}
}
for more information plz read this link http://samwize.com/2015/08/07/how-to-handle-remote-notification-with-background-mode-enabled/
application: didReceiveRemoteNotification:
is called when the OS receives a RemoteNotification and the app is running (in the background/suspended or in the foreground.)
and
application:(UIApplication *)application didFinishLaunchingWithOptions
is called when app is not running and you click on app icon.
1) when my app remove from background and get notification & tap on app icon -
TestViewController *testVC = your_test_vc;
UINavigationController *navVC = [[UINavigationController alloc]initWithRootViewController:testVC];
In TestViewController you can push your controller to show the payload data.
2) Whatever you wrote is correct.
Hope this will help you.
Apps have notification data delivered in a specific set of circumstances. If the user's interaction does not coincide with one of these, the data is not available. There are no exceptions.
the user taps a notification banner (or alert). It does not matter if the app was running or not.
the payload has the content-available field set to a true value, and iOS opts to deliver the notification.
Note: this answer does not explain how to invoke or handle any of the scenarios.
The field value from the userinfo is what you require then under
-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
UIApplicationState state = [[UIApplication sharedApplication] applicationState];
if (state == UIApplicationStateBackground || state == UIApplicationStateInactive)
{ //notification is received when your app is in background
//open the view controller you expected
}
else if(state == UIApplicationStateActive)
{
//notification is received when your app is in foreground
//do nothing
}
}
I've add the background task feature to my application. Here is my app delegate
// AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[self setWindow:[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]];
[self.window setBackgroundColor:[UIColor blackColor]];
if (application.applicationState != UIApplicationStateBackground) {
// Application is launch in because user tap the app icon or from springboard
if ([application respondsToSelector:#selector(setMinimumBackgroundFetchInterval:)]) {
[application setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];
}
} else {
// Application is launch in background mode
}
RootViewController *rootViewController = [[RootViewController alloc] initByDevice];
[self.window setRootViewController:rootViewController];
[self.window makeKeyAndVisible];
return YES;
}
- (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
[[DataManager instance] updateDataWithMaxAttempt:5 block:^(BOOL success, NSArray *newData) {
if (success) {
if ([newData count] > 0) {
completionHandler(UIBackgroundFetchResultNewData);
} else {
completionHandler(UIBackgroundFetchResultNoData);
}
} else {
completionHandler(UIBackgroundFetchResultNoData);
}
}];
}
And this is my root view controller
// RootViewController.m
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
NSLog(#"Did appear");
// Do something that I want it to happen only when the application is visible to user
}
When the user tap the app icon, application works like what I expected. I see "Did appear" in console and stuff is happening after that like I expected.
But when the application awake to perform background task (which not visible to user) the RootViewController's viewDidAppear still getting call because of this line
[self.window makeKeyAndVisible];
By calling makeKeyAndVisible, it makes RootViewController visible even though it's only awake for background task. To fix this, I have to move this line to applicationDidBecomeActive
- (void)applicationDidBecomeActive:(UIApplication *)application {
[self.window makeKeyAndVisible];
}
As a result, the RootViewController's viewDidAppear now being call only when the application is in foreground (visible to user). But, my concern is, when application is in device memory (either active or inactive) applicationDidBecomeActive will be call several times.
user launch app
application become active again from springboard
device unlock
finish call
Is there any issue if I call UIWindow's makeKeyAndVisible several times during the application life cycle?
You can easily ensure that makeKeyAndVisible only happens once: wrap it.
if (!self.window.keyWindow) {
[self.window makeKeyAndVisible;
} else if (self.window.hidden) {
self.window.hidden = NO;
}
I tried to print this value from another class but it is always showing selected tabbar index as 0
Why? is there any other method to identify which tab is selected now?
in my 2nd class tableviewcontroller didselect `method I am trying to print the following line
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
sharedManager=[Mymanager sharedManager];
sharedManager.navigationBarTitle=[name objectAtIndex:indexPath.row];
NSLog(#"%d",self.tabBarController.selectedIndex);
}
but it is always showing 0 ?
I need different index for different tab?
my intention is to identify tabs and call method now i change that to call four different methodes
but when i created one object and tried to call method it not displaying why?
My APpdelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
[NSThread sleepForTimeInterval:5.0];
[[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:UIStatusBarAnimationFade];
[[NSNotificationCenter defaultCenter] postNotificationName:#"internet connection" object:self];
dispatch_queue_t connectivityThread = dispatch_queue_create("com.ttt.test.connectivity", NULL);
dispatch_async(connectivityThread, ^{
while (true){
if([GMMConnectivity hasConnectivity])
[[NSNotificationCenter defaultCenter] postNotificationName:#"InternetCheck" object:[NSNumber numberWithBool:YES]];
else
[[NSNotificationCenter defaultCenter] postNotificationName:#"InternetCheck" object:[NSNumber numberWithBool:NO]];
usleep(5000000);
}
});
return 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.
}
- (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.
}
//-(void)recentPages:(NSString *)pageNumber
//{
// //NSLog(#"%#",pageNumber);
//}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
- (void)applicationWillTerminate:(UIApplication *)application
{
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
Try This
NSUInteger index = [self.tabBarController.tabBar.items indexOfObject:self.tabBarController.tabBar.selectedItem];
NSLog(#"Index: %d", index);
Try this code
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
NSLog(#"selected tab index => %d", appDelegate.tabBarController.selectedIndex);
For TabbarController index change , you need to implement its delegate protocol. This delegate protocol should be implement as a rootviewcontroller or add inside any viewcontroller where you are adding your tabbarcontroller
#pragma mark -
#pragma mark - Tab bar Delegate
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController
{
int i=tabBarController.selectedIndex;
NSLog(#"selected tab index => %d", i);
switch (i) {
case 0:
[tabBarController setSelectedIndex:i];
break;
case 1:
[tabBarController setSelectedIndex:i];
break;
case 2:
[tabBarController setSelectedIndex:i];
break;
case 3:
[tabBarController setSelectedIndex:i];
break;
default:
break;
}
}
At every index of tab bar you have to implement different - different view controller and inside viewWillAppear of every child view controller you can log its index value.
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
AppDelegate *appDel = (AppDelegate *)[[UIApplication sharedApplication] delegate];
NSLog(#"selected tab value=> %d", appDel.tabBarController.selectedIndex);
}
I hope it helps you.
You can take object of tabbar & get selected index of it. Suppose that you have tabbar in application delegate. So, from below code you can get selected index of tabbar.
MyAppDelegate *appDelegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
NSLog(#"%d", appDelegate.tabBarController.selectedIndex);
Make a variable such that it is accessbile in your classes were you want to get the selected item and set the same variable in tabbar delegate.
#interface iPadAppDelegate : NSObject < UITabBarControllerDelegate>
(void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController
(BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController
Hope this helps