How to resume view stack of iphone application - ios

We are implementing one web based application in iPhone. if i answered the incoming phone call my application is relaunching once again instead of resume the application to the state where it last the focus.
we are implementing stack of views. I.e i am maintaining views in a stack manner and each view has the information like images and text information, if i am in 3rd or 4th view, if i answered the incoming phone call my application is relaunching and it is showing 1st view only.
Please tell me how to resume to the 4th view level.

Unfortunately the iPhone doesn't do very much to help you here. I can't guarantee that this will be useful for you but this is how I do it.
In my app I have the following protocol:
#protocol SaveState
- (NSData*) saveState;
- (id) initWithSaveState:(NSData*)data;
#end
Any UIViewController that I need to be able to save its state implements it.
In applicationWillTerminate: I have the following code:
for (UIViewController* vc in self.navigationController.viewControllers) {
if ([vc conformsToProtocol:#protocol(SaveState)]) {
NSArray* state = [NSArray arrayWithObjects:NSStringFromClass([vc class]), [(UIViewController<SaveState>*)vc saveState], nil];
[vcList addObject:state];
}
}
I then save vcList to the NSUserDefaults. To restore the state I have this in applicationDidFinishLaunching::
for (NSArray* screen in screenList) {
UIViewController<SaveState>* next = [[NSClassFromString([screen objectAtIndex:0]) alloc] initWithSaveState:([screen count] == 2) ? [screen objectAtIndex:1] : nil];
if (next != nil) {
[[self navigationController] pushViewController:next animated:NO];
[next release];
}
else {
// error handling
}
}

See Apple's DrillDownSave sample - it demonstrates how to do that.

Related

Correct way to handle application launching with quick actions

I am having a hard time figuring out how to get my quick actions working when I launch my app with a quick action.
My quick actions work, however, if the app was in the background and re-launched with the quick action.
When I try to launch the app straight from the quick action, the app opens as if it was launched by simply tapping the app icon (i.e. it does nothing).
Here is some code from my App Delegate.
In didFinishLaunchingWithOptions:
UIApplicationShortcutItem *shortcut = launchOptions[UIApplicationLaunchOptionsShortcutItemKey];
if(shortcut != nil){
performShortcutDelegate = NO;
[self performQuickAction: shortcut fromLaunch:YES];
}
The method called:
-(BOOL) performQuickAction: (UIApplicationShortcutItem *)shortcutItem fromLaunch:(BOOL)launchedFromInactive {
NSMutableArray *meetings = [self.fetchedResultController.fetchedObjects mutableCopy];
[meetings removeObjectAtIndex:0];
unsigned long count = meetings.count;
BOOL quickActionHandled = NO;
if(count > 0){
MainViewController *mainVC = (MainViewController *)self.window.rootViewController;
if(launchedFromInactive){
mainVC.shortcut = shortcutItem;
}
else{
UINavigationController *childNav;
MeetingViewController *meetingVC;
for(int i = 0; i < mainVC.childViewControllers.count; i++){
if([mainVC.childViewControllers[i] isKindOfClass: [UINavigationController class]]){
childNav = mainVC.childViewControllers[i];
meetingVC = childNav.childViewControllers[0];
break;
}
}
self.shortcutDelegate = meetingVC;
if ([shortcutItem.type isEqual: #"Meeting"]){
NSNumber *index = [shortcutItem.userInfo objectForKey:#"Index"];
[self.shortcutDelegate switchToCorrectPageWithIndex: index launchedFromInactive:NO];
quickActionHandled = YES;
}
}
}
The only action that needs to be performed is that my page view controller (which is embedded inside the meetingVC) should switch to a certain page with respect to the shortcut chosen.
Any ideas on what causes the shortcut to not do anything when using it to launch as opposed to re-opening the app from the background??
I came to realize I was trying to call my methods on a view controller that was not in memory yet. This was causing bizarre behavior in my app. I did have the correct approach to getting access to the view controller and then it dawned on me the possibility of trying to execute the code using GCD.
__block MeetingViewController *safeSelf = self;
contentVC = [self initializeContentViewController: self.didLaunchFromInactive withPageIndex: intIndex];
NSArray *viewControllers = #[contentVC];
dispatch_async(dispatch_get_main_queue(), ^{
[safeSelf.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil];
});
The above worked like magic, and the shortcuts are leading to the correct page. Using a similar approach to mine hopefully yields the desired results for anyone else who wanted to get their shortcuts working by launching the app.

Weird delay in UIPopoverController dismissal

Maybe this is purely simulator related. I have not tried it on an actual device yet.
I'm on the latest greatest MacBook with a 1TB flash drive, and 95% free processor, and less than full memory consumption.
I have a UIPopoverController with 4 items in it, sized to those items.
There's nothing complicated or multi-threaded or long running in any way associated in the UIPopoverController in question.
I've set the appear and dismiss animation at 0, yet when I tap on an item in the list, there seems to be an random indeterminate delay between 0 and .4 seconds in the popover disappearing. Of course the 0 is expected, but the times when it's nearly a half second is very noticeably longer and disconcerting.
Any idea what may be causing that?
Code that shows the popover...
-(IBAction)theLandImpsButtonPressed:(UIButton *)sender
{
iRpNameValuePopover *thePopoverContent = [[iRpNameValuePopover alloc] init];
thePopoverContent.theTableValues = [self getLandImpsChoicesList];
impsLandPopover = [[UIPopoverController alloc] initWithContentViewController:thePopoverContent];
thePopoverContent.thePopoverController = impsLandPopover;
impsLandPopover.popoverContentSize = [iRpUIHelper sizeForPopoverThatHasTitle:NO andListContent:thePopoverContent.theTableValues];
impsLandPopover.delegate = self;
[impsLandPopover presentPopoverFromRect:self.theLandImpsButton.bounds inView:self.theLandImpsButton permittedArrowDirections:UIPopoverArrowDirectionAny animated:NO];
}
Code that dismisses the popover...
BTW, there is no evaluation time incurred here [self userChoiceIsValid] because it simply returns YES right now.
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
_theChosenNameValueItem = [self.theTableValues objectAtIndex:indexPath.row];
[self acceptUserChoiceAndClose];
}
// This contentViewController is encapsulated INSIDE a UIPopoverViewController, and this class cannot itself
// close the popover which contains it, hence the need for the reference to the popover controller
// It is the popover's delegate... the one that created the popover, that is able to close it.
-(void)acceptUserChoiceAndClose
{
_theUserChoseAValue = NO; // Start by assuming they didn't chose a valid value.
if ([self userChoiceIsValid])
{
// Set variable that indicates the user chose a value which can be saved to core data, and/or presented on screen.
_theUserChoseAValue = YES;
// Close the popover.
[_thePopoverController dismissPopoverAnimated:NO];
// Notify the class that presented the popover that the popover has been dismissed.
// It will still be available to the dismissal method where code can retrieve the user's choice, and set the popover to nil.
if (_thePopoverController.delegate && [_thePopoverController.delegate respondsToSelector:#selector(popoverControllerDidDismissPopover:)])
{
[_thePopoverController.delegate popoverControllerDidDismissPopover:_thePopoverController];
}
}
else
{
[self showValidationFailureMessageToUser];
}
}
Dismissing the viewController in main thread will solve the issue.
dispatch_async(dispatch_get_main_queue(), ^{
[self dismissViewControllerAnimated:YES completion:nil];
});
I would check it out in the profiler and see what the time is being spent on.
There's a good tutorial here.
UIPopoverPresentationController *popOverView;
//////
Dismiss it...
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
[popOverView.presentedViewController dismissViewControllerAnimated:NO completion:nil];
popOverView = nil;
});
});

Xcode IOS - How to get the scene/view that is currently being viewed in appdelegate

Okay I am kind of new to IOS development, but I am writing an application where I am using a timer class to time out the user if they idle too long on any particular scene in my storyboard and it bumps the user back to the original scene/view. I have a single story board that is made up of several scenes/views(not sure what the correct word here is), and each scene has its own view controller.
I accomplish the timeout via the appdelegate class. See code below.
So I have the code working and it works great, but I am trying to make it so that it will ignore the timer if we are on the main scene.
I have googled this, read copious amounts of documentation, and have tried many things but so far I haven't been able to figure out how to get the currently viewed scene in the applicationDidTimeout method.
If I can get the name of the currently viewed scene/view, then I can choose to ignore the timer or not.
Does anyone know how to do this?
Thank you for your time.
#import "StoryboardAppDelegate.h"
#import "TIMERUIApplication.h"
#implementation StoryboardAppDelegate
#synthesize window = _window;
-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// applicaiton has timed out
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(applicationDidTimeout:) name:kApplicationDidTimeoutNotification object:nil];
return YES;
}
-(void)applicationDidTimeout:(NSNotification *) notif
{
NSLog (#"time exceeded!!");
UIViewController *controller = [[UIStoryboard storyboardWithName:#"Main" bundle:NULL] instantiateViewControllerWithIdentifier:#"StoryboardViewController"];
UINavigationController * navigation = [[UINavigationController alloc]initWithRootViewController:controller];
[self.window setRootViewController:navigation];
navigation.delegate = self;
navigation.navigationBarHidden = YES;
if (controller) {
#try {
[navigation pushViewController:controller animated:NO];
} #catch (NSException * ex) {
//“Pushing the same view controller instance more than once is not supported”
//NSInvalidArgumentException
NSLog(#"Exception: [%#]:%#",[ex class], ex );
NSLog(#"ex.name:'%#'", ex.name);
NSLog(#"ex.reason:'%#'", ex.reason);
//Full error includes class pointer address so only care if it starts with this error
NSRange range = [ex.reason rangeOfString:#"Pushing the same view controller instance more than once is not supported"];
if ([ex.name isEqualToString:#"NSInvalidArgumentException"] &&
range.location != NSNotFound) {
//view controller already exists in the stack - just pop back to it
[navigation popToViewController:controller animated:NO];
} else {
NSLog(#"ERROR:UNHANDLED EXCEPTION TYPE:%#", ex);
}
} #finally {
//NSLog(#"finally");
}
} else {
NSLog(#"ERROR:pushViewController: viewController is nil");
}
[(TIMERUIApplication *)[UIApplication sharedApplication] resetIdleTimer];
}
#end
I'm assuming you've written the logic for the timer somewhere. Can you just invalidate the timer when you've popped back to the rootViewController?
Also instead of pushing a viewController onto the navigationViewController and handling the errors, you should check to see if the controller you're pushing is already in the stack like so:
if (![navigation.viewControllers containsObject:viewController] {
// push onto the stack
}
You could also check to see how many levels are currently in the navigationController by checking the count of the viewControllers array like so:
if ([navigation.viewControllers count] == 0) {
// I know we're on the main screen because no additional viewControllers have been added to the stack.
}
If you are not using modal controllers anywhere then the simplest solution would be
UINavigationController* nav = (UINavigationController*)self.window.rootViewController; // You could just save the nav as part of your app delegate
if (nav.viewControllers.count > 1){
[nav popToRootViewControllerAnimated:YES];
}
This is different then your current code because your main page will not be deleted and recreated every time the timer goes off
Okay I figured out how to do this. I was making this way too complicated.
To solve this I simply made a property and method in the app delegate class where I could set a scene name.
Then in each view controller header file I import the header file for the app delegate class and define a reference to it. Then in the load event for each view I simply set the scene name in the app delegate class using this line of code.
[myAppDelegate setSceneName:self.title];
Easy peasy!

SKRecognizer returns empty array

I'm using Dragon mobile NDEV SDK, but I have a problem with recognition.
In the root view a button is here to start recording. User can select a row with voice in my table view and then my app push to the corresponding view. But in this view when user press record button again SKRecognizer returns always an empty array, in the second view and even in the first if user go back with back button.
I must restart app to use SKRecognizer again.
Same if I go straight to the second view and I use SKRecognizer, working on this view but if I navigate recognition is not working.
I have a dedicated class for Dragon recognizer, and I instantiate this class in each view with a property for each one.
- (IBAction)recordAction:(id)sender
{
if (_recognizer) {
[_recognizer release];
}
_recognizer = [[DragonRecognizer alloc] init];
[_recognizer startRecord];
}
And the SKRecognizer delegate:
- (void)recognizer:(SKRecognizer *)recognizer didFinishWithResults:(SKRecognition *)results
{
long numOfResults = [results.results count];
transactionState = TS_IDLE;
if (numOfResults > 0)
_result = [[results firstResult] lowercaseString];
[_recognizer release];
_recognizer = nil;
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc postNotificationName:DragonReconizerFinishWithResults object:nil];
}
I receive notification in the view controller:
- (void)stopRecordReconizer:(NSNotification *)n
{
if (![self.navigationController.visibleViewController isKindOfClass:[self class]])
return;
NSString *mainResult = [_recognizer result]; // No result
}
Do you know why SKRecognizer send me an empty array after second usage ?
Thanks for you're help !

How to update a UITableView in detailview when changes the tabbar in the pop over controller?

I am creating an iPad app with splitview, here is the screen shot,
In this one, I want to update the values in the righthand side tableview, when I change the tab in the masterview controller (left hand side). Which will be the good aproach, Should I load another viewcontroller for each tab change? Is it possible? Or just update the table? For all the tab changes i want to display a tableview with different data.
I used following code, i can see the changes in the log, but the table is not getting updated.
- (void)setDetailItem:(id)newDetailItem
{
if (_detailItem != newDetailItem) {
[_detailItem release];
_detailItem = [newDetailItem retain];
// Update the view.
[self configureView];
}
if (self.masterPopoverController != nil) {
[self.masterPopoverController dismissPopoverAnimated:YES];
}
}
- (void)configureView
{
// Update the user interface for the detail item.
if (isStudent) {
textStr = #"student";
NSLog(#"Student....%#",textStr);
[self.tableView reloadData];
}if (isTeachers) {
textStr = #"teacher";
NSLog(#"Teacher....%#",textStr);
[self.tableView reloadData];
}if (isPreference) {
textStr = #"preference";
NSLog(#"Preference....%#",textStr);
[self.tableView reloadData];
}if (isConfiguration) {
textStr = #"configuration";
NSLog(#"Configuration....%#",textStr);
[self.tableView reloadData];
}
}
I am also tried
[self performSelectorOnMainThread:#selector(refreshTableView) withObject:nil waitUntilDone:NO];
Please share your ideas.
thanks :)
At last I found the issue myself, and I was really great to search for a solution. I have went through several forums and tutorials. Finally I figured out the issue. And in this video, they have illustrated how to create a SplitView application using XCode 4.2. In this one, just one line of code fixed the issue. That is in the Appdelegate.m file. In default, the MasterViewController doesn't have access to the detail view, so if we need to do something on the detailview, we have to connect masterviewcontroller and detailviewcontroller. Check the video, then you will (those who are facing the same issue) understand.
Thanks :)

Resources