I'm using my appDelegate's applicationDidReceiveMemoryWarning method to dump some high resource objects. When the app starts back up, rather than trying to reload just those objects and return the user to his last page, I would like just to restart the app from the top (from the main page, the app only goes one level deep, so this is totally acceptable to us).
Here's my paltry attempt, but it was an abject failure. It got close, but I ended up introducing some problems that resulted in an actual memory dump and app crash.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.viewController = [[SplashScreenViewController alloc] initWithNibName:#"SplashScreenViewController" bundle:nil];
UINavigationController *navcon = [[UINavigationController alloc] initWithRootViewController:self.viewController];
self.window.rootViewController = navcon;
[self.window makeKeyAndVisible];
return YES;
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
//For testing purposes only
self.lowMemoryWarning = TRUE;
NSLog(#"app did enter background");
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
NSLog(#"app will enter foreground");
if (self.lowMemoryWarning) {
NSLog(#"recovering from low memory warning");
self.window.rootViewController = nil;
UINavigationController *navcon = [[UINavigationController alloc] initWithRootViewController:self.viewController];
self.window.rootViewController = navcon;
[self.window makeKeyAndVisible];
}
}
What's the best approach for doing something like this? Is there maybe a simple trick that I don't know about?
Thank you!
Do you mean you want the app to restart each time it started (or enter foreground) ? If yes, maybe you can just set the app to not support multi tasking
http://developer.apple.com/library/ios/#documentation/iphone/conceptual/iphoneosprogrammingguide/ManagingYourApplicationsFlow/ManagingYourApplicationsFlow.html#//apple_ref/doc/uid/TP40007072-CH4-SW5
Search for "Opting out of Background Execution"
================================================================================
Ah sorry, I didn't know the nature of your application, if some process need to be run on the background then this method is no go.
I read your comment above about the UIActivityIndicator + loading the certain objects back on the current view after app enter foreground. Maybe this thread can help you, Finding the current view when application enter foreground. IOS
Related
I have an the app when notification is fired i get a notification bar when the app is in background ,when i tap on that bar it navigates into tableview of the notification set . When i quit the app from background i am receiving notification but when tap on the notification bar the app is getting crashed since its not getting indexpath of the tableview.
When the app is quit in background and reloading the app should enter didfinishlaunching.
In appDidFinishLaunching i am calling the method which navigates into tableview
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.viewController = [[PPViewController alloc] initWithNibName:#"ViewController_iPhone" bundle:nil] ;
UILocalNotification *notification = [launchOptions objectForKey: UIApplicationLaunchOptionsLocalNotificationKey];
if (notification)
{
int remedyID = [[notification.userInfo objectForKey:kRemindMeNotificationRemedyIDKey] intValue];
NSDictionary *reminderDetails =[NSDictionary dictionaryWithObjectsAndKeys:notification.userInfo,#"kRemindMeNotificationDataKey",[NSNumber numberWithInt:remedyID],kRemindMeNotificationRemedyIDKey,nil];
[_viewController goToReminder:reminderDetails showNotification:YES];
}
[application setStatusBarStyle:UIStatusBarStyleBlackTranslucent];
self.viewController = [[PPViewController alloc] initWithNibName:#"PPViewController" bundle:nil];
self.window.rootViewController = self.viewController;
[self.window makeKeyAndVisible];
return YES;
}
**This is the code which navigates into tableview in another viewcontroller**
- (void)goToReminder:(NSMutableDictionary *)reminderDictionary showNotification:(BOOL)shouldShowNotification
{
NSIndexPath *selectedSymptIP = [NSIndexPath indexPathForRow:selectedSymptomIndex inSection:keyIndexNumber];
[self tableView:symptomsTableView didSelectRowAtIndexPath:selectedSymptIP];
}
When you quit the app and your app starts again, your viewController is not yet set up. When launching from a notification, you need to check if you are resuming from the background, or launching normally.
Look at applicationWillEnterForeground and applicationDidBecomeActive in the UIApplicationDelegate protocol reference for how to handle resuming your app from various states. Also see: http://developer.apple.com/library/ios/#documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/ManagingYourApplicationsFlow/ManagingYourApplicationsFlow.html#//apple_ref/doc/uid/TP40007072-CH4-SW3
I was able to solve the problem ..the crash was occurring since wrong xib file was loaded .
I am developing an app in Xcode, I got it working and was putting some finishing touches.
Then I upgraded Xcode to the latest version, changed a bit the code for the Autorotate options (my app shouldn't autorotate) and fiddled with the supported rotation (landscape vs. portait).
Then suddenly my debug in iOs simulator just shows a black screen. The app builds fine but nothing is shown in the simulator....
And I can't really understand why...
Using Xcode 4.6
i don't have an ApplicationDidFinishLoading but I have this:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[window addSubview:viewController.view];
[window makeKeyAndVisible];
return YES; }
Use Below Code instead in your ApplicationDidFinishLoading
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
self.viewController = [[YourFirstViewController alloc] initWithNibName:#"YourFirstViewController" bundle:nil];
self.window.rootViewController = self.viewController;
[self.window makeKeyAndVisible];
return YES;
}
This may solve your problem. Please try it.
commenting the "shouldAutorotateToInterfaceOrientation" solved my problem.
I have implemented iOS 6 API for state saving, it works - after I quit the app and launch back in for some milliseconds the restored view controller fly in, but then it's replaced by the main view controller I display at launch.
I'm setting every time the app launch the root view of the main window, so this must be the issue.
Here is my code:
- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[self commonInitializationLaunching:launchOptions];
return YES;
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[self commonInitializationLaunching:launchOptions];
return YES;
}
- (void)commonInitializationLaunching:(NSDictionary *)launchOptions
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
static NSString *const kKeychainItemName = #"OAuthGoogleReader";
self.viewController = [[ViewController alloc] initWithNibName:#"ViewController" bundle:nil];
self.navController = [[UINavigationController alloc] initWithRootViewController:self.viewController];
GTMOAuth2Authentication *auth;
auth = [GTMOAuth2ViewControllerTouch authForGoogleFromKeychainForName:kKeychainItemName
clientID:kClientID
clientSecret:kClientSecret];
self.window.rootViewController = self.navController;
[self.window makeKeyAndVisible];
BOOL isSignedIn = [auth canAuthorize];
if (isSignedIn) {
NSLog(#"Signed");
}else{
NSString *scope = #"https://www.google.com/reader/api/";
GTMOAuth2ViewControllerTouch *viewController;
viewController = [[GTMOAuth2ViewControllerTouch alloc] initWithScope:scope
clientID:kClientID
clientSecret:kClientSecret
keychainItemName:kKeychainItemName
delegate:self
finishedSelector:#selector(viewController:finishedWithAuth:error:)];
[self.navController pushViewController:viewController animated:YES];
// self.window.rootViewController = viewController;
}
});
}
You can see that in -(void)commonInitializationLaunching:(NSDictionary *)launchOptions
I'm setting my window's root view. I don't know what to put in there. Perhaps check if there is saved state and then load this method? But how?
Thanks!
Here is what I've tried following Rob's advice:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
if (!self.isRestored) {
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
}
[self commonInitializationLaunching:launchOptions];
[self.window makeKeyAndVisible];
return YES;
}
with nothing in willFinishLaunching...
I also removed by window code from my commonInitializationLaunching method.
Storyboards will do most of the heavy lifting for you, such as restoring the window. Using code, however, will not restore the window. You will need to hold on to your root view controller using the encoder. Your code will look something like this:
NSString * const AppDelegateRootVCKey = #"AppDelegateRootVCKey";
- (void)application:(UIApplication *)application willEncodeRestorableStateWithCoder:(NSCoder *)coder {
[coder encodeObject:self.window.rootViewController forKey:AppDelegateRootVCKey];
}
- (void)application:(UIApplication *)application didDecodeRestorableStateWithCoder:(NSCoder *)coder {
// Grabs the preserved root view controller.
UIViewController * vc = [coder decodeObjectForKey:AppDelegateRootVCKey];
if (vc) {
UIWindow * window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
window.rootViewController = vc;
window.restorationIdentifier = NSStringFromClass([window class]);
// The green color is just to make it obvious if our view didn't load properly.
// It can be removed when you are finished debugging.
window.backgroundColor = [UIColor greenColor];
self.window = window;
}
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
if (!self.window) {
UIWindow *window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// The blue color is just to make it obvious if our view didn't load properly.
// It can be removed when you are finished debugging.
window.backgroundColor = [UIColor blueColor];
UIViewController *root = // However you create your root.
window.rootViewController = root;
window.restorationIdentifier = NSStringFromClass([window class]);
self.window = window;
}
[self commonInitializationLaunching:launchOptions];
[self.window makeKeyAndVisible];
return YES;
}
Another gotcha to watch out for is to make sure that your UINavigationControllers and UITabBarControllers have restoration identifiers.
State restoration is generally integrated with storyboards. If you're using a storyboard, you should not be creating your own window, view controllers, etc. You should let the storyboard do this for you. What's happening is that the storyboard is doing all the state restoration, and then you're creating a new window and laying it on top of all that. If that's the case, you're probably creating two copies of your UI on every launch. You're just not noticing it.
If you are constructing your entire interface in code (not a recommended approach, but it does work), then you need to determine whether state restoration happened before creating your UI. This is fairly simple:
In your commonInitializationLaunching:, initialize only non-UI elements (things that wouldn't ever be in state-preservation). This is the place to handle things that the UI elements might rely on during state restoration. You don't have any of these in your current code.
In application:didDecodeRestorableState:, set an app delegate ivar to indicate that state was restored.
In application:didFinishLaunchingWithOptions:, after running commonInitializationLaunching:, check your ivar. If state wasn't restored, create a UI.
Do remember that the commonInitializationLaunching: pattern only exists for backward compatibility with iOS 5. If you don't need that, then just put non-UI in willFinish and UI in didFinish (if state wasn't restored).
Please tell me can I use a time while application is loading (while splash screen is being showed) to perform some background operations? (I need to call CLLocationManager and update current location) If I'm allowed to do it please tell me where to put a code.
It is not possible to perform anything while real splash screen is shown.
Your actions starts in
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
method which is executed when splash screen removed.
If you want to perform some time-cost operation before showing user interface, your only options is show manually "fake" splash screen during this time. It can be image or anything else e.g. activity indicator or animation. If you use same image that used for splash screen,
user will see no difference, it will look like splash screen will remain some seconds longer for him, but you will already have your data loaded.
In this case your app delegate may be like this:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.viewController = [[ViewController alloc]
initWithNibName:#"FakeSplashViewController" bundle:nil];
self.window.rootViewController = self.viewController;
[self performSelectorInBackground: #selector(someLongOperation) withObject: nil];
[self.window makeKeyAndVisible];
return YES;
}
- (void) someLongOperation{
//doing something
//...
[self performSelectorOnMainThread:#selector(atLastLoadGUI) withObject:nil waitUntilDone:NO];
}
- (void) atLastLoadGUI{
// start GUI
}
I converted my app to ARC and removed all the pre-build release errors. It launches, but will crash (EXC_BAD_ACCESS) as soon as I call any method (all of which are attached to UIButtons). I also noticed that it will ask if the user will allow for the app to use the user's location, but the alert will disappear before the user can answer yes or no.
I feel like there's some very basic setting I'm missing causing this.
Here's the first method called, it won't let the user actually say if they'll allow location services. The alert fires then disappears. Does this help anyone's diagnosis?
-(void)startLocation
{
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
[locationManager startUpdatingLocation];
}
Also, here's my didFinishLaunchingWithOptions method
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
UINavigationController *nav = [[UINavigationController alloc] init];
StartPageViewController *start = [[StartPageViewController alloc]init];
NSManagedObjectContext *context = [self managedObjectContext];
if (!context)
{
// Handle the error.
}
start.managedObjectContext = context;
nav.viewControllers = [NSArray arrayWithObjects:start, nil];
[_window addSubview:[nav view]];
[self.window makeKeyAndVisible];
return YES;
}
Try retaining your navigation controller by making it a strong property on your delegate.
At the moment, I don't see any code that would cause ARC not to release nav at the end of the method. That would release start, which would release context.
All I needed to change (so far) was:
self.window.rootViewController = nav;
instead of:
[_window addSubview:[nav view]];