iOS : is it possible to open previous viewController after crashing and re-launch app? - iphone

How to achieve this stuff below? Please give me some guidance for it. I describe my issue below.
When I tap home button and remove app from tray and while I am opening app I get the login screen. I know how to use NSUserDefaults well.
But my issue is that when I navigate 3rd or 4th viewController and I press Home Button and remove app from tray, Then whenever I open app than I want to open with last open viewController.
Also same when my app is Crashing and I am opening it again then I want to open app with last open viewController state.
So I just want to know that is that possible or not? If yes, then please guide me how to achieve this stuff.
Thank you

Yes, both cases are possible.
On crash, you can use UncaughtExceptionHandler to perform some code. In you app delegate, register you handler like this:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSSetUncaughtExceptionHandler(&uncaughtExceptionHandler);
// Other didFinishLaunchingWithOptions code
And add your handler method to the same .m file
void uncaughtExceptionHandler(NSException *exception)
{
// App crashed, save last selected tabbar index to the to the NSUserDefaults
[[NSUserDefaults standardUserDefaults] setInteger:tabBarController.selectedIndex forKey:#"LastSelectedTabbarIndex"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
While app runs, to keep track of last selected tab bar controller, use UITabBarControllerDelegate and save newly selected tabbar's index to NSUserDefaults. Short example:
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController
{
NSUInteger tabIndex = [[tabBarController viewControllers] indexOfObject:viewController];
// I have newly selected index, now save it to the NSUserDefaults
}
This code will save last selected tabbar's index to the NSUserDefaults every time tabbar's selected index changes.
Finally, when you app starts (in your didFinishLaunchingWithOptions), read last saved tabbar index from NSUserDefaults and set tabbar's selected index accordingly
self.tabBarController.selectedIndex = lastSelectedIndexFromDefaults;
Edit:
If you also need to restore UINavigationControllers controllers stack, its pretty difficult task. I give you just a quick overview what comes to my mind.
There are 2 cases:
You have custom view controllers initializers and need to pass custom object to those controllers - In this case, its almost impossible (in some reasonable time) implement this
You use only -init or -initWithNibName...: to initialize view controllers in navigation stack. You could enumerate controllers from the root UINavigationController of the tab, get their classes names using NSStringFromClass and save them to NSUserDefaults. On apps start, you would reverse procedure (initialize controllers using their names strings read from NSUserDefaults using something like this: UIViewController *vc = [[NSClassFromString(#"aa") alloc] init];).

I understand you are ok with the code part so i will just give my suggestion
on viewDidLoad of every view controller set a nsuserdefault value of the top most object on navigation array.
if their are not too many branches then you can manage the push at root view controller easily

This is not the proper answer but you can use it for Navigating view after launching.
In AppDelegate file use below codes:---
#import "NewSAppDelegate.h"
#import "NewSViewController.h"
static NewSAppDelegate *globalSelf;
#implementation NewSAppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
self.viewController = [[NewSViewController alloc] initWithNibName:#"NewSViewController" bundle:nil];
self.navController=[[UINavigationController alloc] initWithRootViewController:self.viewController];
self.window.rootViewController = self.navController;
[self.window makeKeyAndVisible];
globalSelf=self;
NSSetUncaughtExceptionHandler(&uncaughtExceptionHandler);
return YES;
}
void uncaughtExceptionHandler(NSException *exception)
{
UIViewController *currentVC = globalSelf.navController.visibleViewController;
[[NSUserDefaults standardUserDefaults] setObject:NSStringFromClass(currentVC.class) forKey:#"lastVC"];
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
UIViewController *currentVC = self.navController.visibleViewController;
[[NSUserDefaults standardUserDefaults] setObject:NSStringFromClass(currentVC.class) forKey:#"lastVC"];
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"appDidBecomeActive" object:nil];
// 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.
}
In your login viewController's init method add an observer for notification and in notification method , you can apply if conditions for viewController's name received.and push to that viewController on launching LoginView controller as:---
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(openLastVC)
name:#"appDidBecomeActive"
object:nil];
// Custom initialization
}
return self;
}
-(void)openLastVC
{
NSLog(#"val ==%#",[[NSUserDefaults standardUserDefaults] valueForKey:#"lastVC"]);
if ([[[NSUserDefaults standardUserDefaults] valueForKey:#"lastVC"] isEqualToString:#"GhachakViewController"]) {
GhachakViewController *gvc=[[GhachakViewController alloc] initWithNibName:#"GhachakViewController" bundle:nil];
[self.navigationController pushViewController:gvc animated:NO];
}
}
May this help you....

Related

iOS Accessing views data from delegate without allocating a new view

I need to change a data (a label) from the app's delegate method ApplicationDidEnterForeground without allocating a new view. The view is called "Reminder", so I imported it into the delegate and I can access its data only if I allocate it (Reminder *anything = [Reminder alloc...etc), but since I want to change the current view loaded I need to have direct access to the view that's already loaded.
How would I do to change the main view's label from the delegate as soon as my application enters foreground?
obs: I know I can do it on -(void)ViewDidLoad or -(void)ViewWillAppear but it won't solve my problem, since it won't change the label if, for example, the user opens the app through a notification box (slide icon when phone is locked). In that case, none of the above methods are called if the app was open in background.
I don't know if I was clear, hope I was. Thank you in advance.
IF you are using storyboards, you can do this to access the current view being seen
- (void)applicationDidEnterBackground:(UIApplication *)application
{
UINavigationController *a=_window.rootViewController;
Reminder *rem = a.topViewController;
rem.label.text=#"test";
}
IF not using story boards
When I create views that I need to access later, I define them as a property, like this
on AppDelegate.h
//#interface SIMCAppDelegate : UIResponder <..........>
//{
//Some variables here
//}
//Properties here
#property (strong, nonatomic) Reminder *reminder;
//Some method declaration here
//eg: -(void) showSomething;
on AppDelegate.m
//#implementation AppDelegate
#synthesize reminder;
so when I alloc/init the view like this
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//codes before here
self.reminder = [[Reminder alloc] init];
self.reminder.label.text = #"OLD LABEL";
//codes after here
}
I will be able to access it again after allocation on other methods, like this
- (void)applicationWillEnterForeground:(UIApplication *)application
{
self.reminder.label.text = #"NEW LABEL";
}
just send a notification from your ApplicationDidEnterForeground: method and receive it on that class where you want to update the label... Like this..
//Your ApplicationDidEnterForeground:
[[NSNotificationCenter defaultCenter] postNotificationWithName:#"UpdateLabel" withObject:nill];
and add observer in it viewDidLoad: of that controller where you want to update label
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(updateLabel:)
name:#"UpdateLabel"
object:nil];
made your method in same class ...
- (void)updateLabel:(NSNotification *)notification{
update label
}
Might be you can try following code -
NSMutableArray *activeControllerArray = [self.navigationController.viewControllers mutableCopy];
for (int i = 0; i< [activeControllerArray count]; i++) {
if ([[activeControllerArray objectAtIndex:i] isKindOfClass:[Reminder Class]) {
Reminder *object = [activeControllerArray objectAtIndex:i];
//Perform all the task here which you want.
break; //Once found break the loop to do further processing.
}
}

how to display a alert display only on the application launch first time?

Ha ii everybody i am doing a reader application which has so many functionality in it,pinch gesture for search function,swipe right and left for the previous and next page,tap to hold for the chapter selection view like that,but when the user download the application and use it we have to inform these functionality with a alert-view or a simple pop-up for the application very first launch.I saw it in many reader applications ,i know this is done through NSNotification or something like that,but i dont know how to use this ,please help me to do this.
Thanks in advance.
In case you mean a UIAlertView, thats pretty easy. But if you want a nice looking view notifying the user about different features, maybe add a view controller of which view has all these things and a get started button on it.
Use NSUserDefaults to store if the user has entered the application for the first time as in the link which EI Developer suggested.
In your AppDelegate Class add your changes to this method.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[[NSUserDefaults standardUserDefaults] registerDefaults:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES],#"firstLaunch",nil]];
//If First Launch
if ([[NSUserDefaults standardUserDefaults] boolForKey:#"firstLaunch"]) {
//Show welcome view
[self.window addSubview:[welcomeScreen view]];
}
else {
[self.window addSubview:[startUpViewController view]];
}
[self.window makeKeyAndVisible];
}
Add another method in your AppDelegate which the welcomeScreen class can call when the user presses the get started button
- (void) getStarted {
[[welcomeScreen view] removeFromSuperview];
[self.window addSubview:[startUpViewController view]];
}
In your welcomeScreen class add an IBAction which calls this method.
- (IBAction) getStartedPressed {
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate getStarted];
//set firstLaunch to NO
[[NSUserDefaults standardUserDefaults] setBool:NO forKey:#"firstLaunch"];
}
Dont forget to add the AppDelegate #import header in your welcomeScreen class
You can probably use the code found in this question:
iPhone: How do I detect when an app is launched for the first time?
Hope it helps! :D

Login Screen with Storyboarding possible?

I am playing around with the new iOS 5 features and trying to rewriting one of my apps as pure iOS 5 app using the new storyboarding feature.
To cut a long story short, I have a start screen where the app tries to connect to a server if the user saved some login data, if not, it should ask for them.
Here is how I would do it. I create a Viewcontroller which is doing the connection thing in the viewDidLoad method. If there is no login data or the login is not successful, I need a to do a manual segue to the login screen.
Now is this even possible, or do I need 2 story boards for that ?
I have solved it by putting a login view without any segues (to or from it) like in the screenshot below:
Then, I used a custom class in the tab bar controller to show it whenever I need it.
In the tab bar controller class, I use 'viewDidLoad' to fire up the login view. To show the modal view, I do have a singleton thingy that stores some state, say BOOL isAuthenticated, where I do the magic:
- (void) performLoginIfRequired: (UIViewController *) source {
if (!self.isAuthenticated) {
NSLog(#"Is not authed");
UIStoryboard *storyboard = [UIApplication sharedApplication].delegate.window.rootViewController.storyboard;
UIViewController *loginController = [storyboard instantiateViewControllerWithIdentifier:#"loginScreen"];
[source presentModalViewController:loginController animated:YES];
} else {
NSLog(#"Is authe");
}
}
And, in my case, I wanted it to be shown when the app first starts, but also when it enters foreground again. So, I registered my tab bar controller with the notification center, so I get notified if the app is coming back:
-(void)viewDidLoad {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(willEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
}
In the willEnterForeground method, I do:
-(void) willEnterForeground: (NSNotification *)notification {
[[myStateThingy defaultState] performLoginIfRequired:self];
}
It sounds like you need to use the performSegueWithIdentifier method. Make sure both views are in the same storyboard, link them together using a Push segue, and give that segue a name. Then, from your first view controller's code simply call the performSegueWithIdentifier to perform a manual segue.
Hope this helps!
See also: Conditionally following a segue
Cheers,
Jesse L. Zamora
I had this same issue, and I solved it simply by doing the following: Instead of trying to segue to a login screen(modally or push), I made the login screen my root view controller. In the login view controller's viewWillAppear method, I check if someone's logged in already. If so, I push my home screen:
// mutableFetchResults is an array with my persistent Credentials object
if ([mutableFetchResults count] > 0) { // Someone's already logged in
[self performSegueWithIdentifier:#"Home" sender:self];
}
Also, in the Home screen view controller's viewWillAppear method, I hid the back button with this line, so the user can't go "back" to the login screen:
self.navigationItem.hidesBackButton = YES;
Finally, every page of my app has a "Sign Out" bar button on the top right. Signing out and putting the login screen up was as simple as this:
- (IBAction)signOutButtonPressed:(UIBarButtonItem *)sender {
MyAppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
[appDelegate signOutCurrentUser]; // this method in my app delegate deletes the current Credentials
[self.navigationController popToRootViewControllerAnimated:YES];
}
Hope that was simple enough!
After trying many different methods, I was able to solve this problem with this:
-(void)viewWillAppear:(BOOL)animated {
// Check if user is already logged in
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
if ([[prefs objectForKey:#"log"] intValue] == 1) {
self.view.hidden = YES;
}
}
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
// Check if user is already logged in
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
if ([[prefs objectForKey:#"log"] intValue] == 1) {
[self performSegueWithIdentifier:#"homeSeg3" sender:self];
}
}
-(void)viewDidUnload {
self.view.hidden = NO;
}

Changing view from one xib to another xib with animation after some times

I have made a view based application which is loading a default view ...
My default view is a splash screen ..
What I want to achieve is once default view (splash view) finished loading, after few seconds it loads another view which is either a privacy policy or application screen.
Code in AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
self.window.rootViewController = self.viewController;
[self.window makeKeyAndVisible];
return YES;
}
No Change as usual ...
Above code load a view from splashscreen.xib file
Following code is in splashscreen.m
- (IBAction)loadPrivacyScreen {
NSLog(#"Switching To Another View");
PrivacyPolicyView *modal = [[PrivacyPolicyView alloc]initWithNibName:nil bundle:nil];
modal.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:modal animated:YES];
[modal release];
}
- (void)viewDidLoad {
[super viewDidLoad];
sleep(3);
[self loadPrivacyScreen];
// Do any additional setup after loading the view from its nib.
}
After three second it does get in to the loadPrivacyScreen funciton but doesn't load the view.
- (IBAction)loadPrivacyScreen;
I have created a method as IBAction because I want to hook that method with a button on privacy screen to check that function works ...
And surprisingly it works when you click the button. But it doest work on time.
Can anyone suggest me what am I doing wrong ?? or any other alternative to achieve same thing??
Note: I have also try changing
- (IBAction)loadPrivacyScreen;
to
- (void)loadPrivacyScreen;
But still same result. It is not switching ....
First of all iOS provides a simple way to load a splash screen.
Just add a Image with 320x480 resolution in the name called default.png and add that to your project it will automatically use this image as splash screen image.
In your way call the loadPrivacy screen with a timer.
- (void)viewDidLoad {
[super viewDidLoad];
NSTimer *theTimer = [NSTimer scheduledTimerWithTimeInterval:3.00 target:self selector:#selector(loadPrivacyScreen) userInfo:nil repeats:NO];
}
- (void)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
self.window.rootViewController = self.viewController;
[self.window makeKeyAndVisible];
[viewController showSplash];
}
And in the view controller, define a method like
> IBOutlet UIView *modelView;
- (void)showSplash;
- (void)hideSplash;
-(void)showSplash {
UIViewController *modalViewController = [[UIViewController alloc] init];
modalViewController.view = modelView;
[self presentModalViewController:modalViewController animated:NO];
[self performSelector:#selector(hideSplash) withObject:nil afterDelay:2.0];
}
//hide splash screen
- (void)hideSplash {
[[self modalViewController] dismissModalViewControllerAnimated:YES];
}

Refresh UINavigationController?

I have a UINavigationController with two ViewControllers on the stack. At a certain point in the program execution, the second view controller is visible on the screen and at that moment, I would like to replace that ViewController with another. However, it's not working. Here is my code:
UINavigationController * thisNavController = self.waitingController;
// remove the Dummy and set the new page instead
NSMutableArray * newControllers = [NSMutableArray arrayWithArray: thisNavController.viewControllers];
[newControllers replaceObjectAtIndex: ([thisNavController.viewControllers count] - 1) withObject: page];
NSLog (#"visible before: %#", [thisNavController.visibleViewController description]);
[thisNavController setViewControllers: [NSArray arrayWithArray: newControllers] animated: YES];
NSLog (#"visible after: %#", [thisNavController.visibleViewController description]);
[thisNavController.visibleViewController.view setNeedsDisplay];
The above code produces this output:
2011-05-05 13:30:22.201 myApp[3286:207] visible before: <DummyViewController: 0x4c8b4c0>
2011-05-05 13:30:22.209 myApp[3286:207] visible after: <RealViewController: 0x60173f0>
But what is shown on the screen does not change. It seems that everything works fine after I switch tabs, so it seems that it is a redrawing problem, but setNeedsDisplay does nothing and I couldn't find a method that tells the NavigationController that its viewControllers have changed.
Is there some refresh mechanism that I have to trigger to refresh the screen?
One solution would be to say add 2 (initial) view controllers when your app is started, and only allow navigation from the 2nd and 3rd ones, falling back to the 1st (root) view controller in your senario described. You never allow navigation back to this 1st view controller or from this 1st view controller to the 2nd; you see this sort of behaviour in some of Apple's apps, like iTunes and Remote - if there's no network connect the app shows a no-network connection view immediately.
So, when you want to show the 1st view controller above, you do something like:
NSArray *array = [navigationController popToRootViewControllerAnimated:NO];
Without more info about the navigation behaviour of your app I hope this helps.
Or show a modal view controller?
The problem turned out to be the fact that I was trying to replace the view controller stack before the initial transition animation for the Dummy controller has finished. This can be prevented in the following manner.
First, preserve the (eventual) delegate, set the current object as the delegate, set a flag that animation is in progress and push the new controller:
self.oldNavigationControllerDelegate = self.waitingController.navigationController.delegate;
self.waitingController.navigationController.delegate = self;
self.isAnimating = YES;
[viewController.navigationController pushViewController: [[DummyViewController alloc] init] animated: YES];
Then, implement the UIViewControllerDelegate protocol methods as follows:
#pragma mark -
#pragma mark UINavigationControllerDelegate methods
- (void) navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
if (navigationController == self.waitingController.navigationController)
self.isAnimating = YES;
}
- (void) navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
if (navigationController == self.waitingController.navigationController) {
self.isAnimating = NO;
if (self.readyPage != nil)
[self pageIsReady: self.readyPage]; // method to load the ready controller
}
}
After that, whenever your content/controller/download/whatever is ready, make sure that the navigation controller is no longer animating. If it is, set a flag that the page is ready. If it isn't, load the page:
if (self.isAnimating)
self.readyPage = controller;
else
[self pageIsReady: controller];
And, of course, implement the actual loading of the new stack (as usual):
- (void) pageIsReady: (UIViewController *) page {
// this method should replace the dummy that is spinning there
UINavigationController * thisNavController = self.waitingController.navigationController;
// remove the Dummy and set the new page instead
NSMutableArray * newControllers = [NSMutableArray arrayWithArray: thisNavController.viewControllers];
[newControllers replaceObjectAtIndex: ([thisNavController.viewControllers count] - 1) withObject: page];
thisNavController.viewControllers = [NSArray arrayWithArray: newControllers];
thisNavController.delegate = self.oldNavigationControllerDelegate; // restore the original delegate
// clean up
self.isAnimating = NO;
self.readyPage = nil;
self.waitingController = nil;
self.oldNavigationControllerDelegate = nil;
}
This makes everybody happy :P