When my app receives a notification it handles it like this opening a specific view:
UILocalNotification *localNotif = [launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];
if (localNotif) {
NSLog(#"Recieved Notification didFinishLaunchingWithOptions %#",localNotif);
[theLastPresentedViewController presentViewController:myVC animated:YES completion:nil];
}
Basically my question is how to get access to the last presented ViewController theLastPresentedViewController so I can present myView on top of it ?
While the app uses a tabBarViewController as the basis, I present other viewControllers on top of other views so there is no way to know which ViewController has beed presented as the last one.
Do I have to manually remember each ViewController I present, so when the app launches I can access the last one or is there a way to directly access the last visible viewController of the app ?
Since the user can just press the HOME Button and exit the app there is no way to know which view was shown at this moment. It would also be OK to dismiss all these views from the stack until the tabBarViewController is reached.
Many thanks!
It's basically a bad idea to perform "random" pushes onto navigation stack. You could present a semi-modal or modal controller as a reaction to your notification, and it does not generally require the pointer to "top visible" view controller.
Alternatively, you can allow your view controllers / views to accept first responder, like:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self becomeFirstResponder];
}
- (BOOL)canBecomeFirstResponder
{
return YES;
}
Now, you can send an event:
[[UIApplication sharedApplication] sendAction:#selector(popNotification) to:nil from:notificationObj forEvent:nil];
And it will be dispatched to first responder (that is any view in focus, and down to view controller, as the view cannot handle the custom event). From this point you are in a context of top visible view controller and can proceed with your original task.
I found this to access the top most window/view:
[[[UIApplication sharedApplication] keyWindow] addSubview:someView]
- (void)applicationDidEnterBackground:(UIApplication *)application{
saveIndex = self.tabBarController.selectedIndex //shows the current index of the viewController of the Array of the tabBarController
//add some stuff to save the integer}
You could save this integer when the User quits the App and after the app becomes active load the tabBarController with this element of the Array
To open the saved view write this in your AppDelegate:
- (void)applicationDidBecomeActive:(UIApplication *)application{
[self.tabBarController setSelectedIndex:savedIndex];}
This should do it
Related
So I am having an issue with my apps logic in whether or not, based on a user's credentials, the main view of my app should display a tableview over the main view after the login view has been dismissed or just display the main view after the login view has been dismissed.
So currently, I send an http post request to a server where I get a token back and then make another call after that to get the amount of list items the user has, lets say games.
If the user only has ONE game, I want the main view to be displayed, but if the user has multiple games, then the user should see a tableview, press on one of the cells then modally dismiss the tableview to show the mainview.
Typically I have these 3 controllers, at least this is the way I'm thinking about how the implementation should be, not sure if its the correct way.
So assume that controller A is the login, controller B is the tableview, and controller C is the mainview.
Controller C is in the bottom of the stack, whereas controller A is at the top. But potentially, controller B may or may not be displayed based on the information from controller A. When I say bottom of the stack, I mean if the user isn't already logged in, then the login view (controller A) will be presented without animation over the mainview (controller C). So technically, the main view(controller C) is always loaded but data has not been fed to it, only after the login view has been dismissed by correctly submitted credentials.
Here are a few methods implented in the mainviewcontroller (controller C) to check if the user has logged in, if they haven't then the app technically goes through the mainviewcontroller first then presents the login view. I understand that this is the way to go for login schemas with app development but I am not 100%:
- (void)showLoginViewAnimated:(BOOL)animated {
NSLog(#"[MainViewController] Show login view controller");
PowerOneLoginTabBar *loginVC = [[PowerOneLoginTabBar alloc] init];
[self presentViewController:loginVC animated:NO completion:nil];
}
- (void)logoutHandler:(NSNotification *)notification {
NSLog(#"[MainViewController] Logout handler");
[[NSUserDefaults standardUserDefaults] setBool:NO forKey:#"userLoggedIn"];
[[NSUserDefaults standardUserDefaults] synchronize];
[self showLoginViewAnimated:YES];
}
In viewdidLoad of the mainview (viewcontroller C):
BOOL isUserLoggedIn = [[NSUserDefaults standardUserDefaults] boolForKey:#"userLoggedIn"];
if( !isUserLoggedIn || (!setAuthenticationKey || [setAuthenticationKey isKindOfClass:[NSNull class]]))
[self showLoginViewAnimated:NO];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(logoutHandler:) name:LOGOUT_NOTIFICATION object:self.view];
Here's my login handler implemented in the login viewcontroller (controller A):
- (void)authenticateHandler:(NSNotification *)notification {
NSLog(#"[LoginViewController] Authenticate handler");
NSDictionary *userDict = [notification userInfo];
BOOL isUserAuthenticated =
//[username isEqualToString:[userDict objectForKey:#"username"]] &&
//[password isEqualToString:[userDict objectForKey:#"password"]] &&
[api_Key isEqualToString:[userDict objectForKey:#"apiKey"]] &&
([_auth isEqualToString:[userDict objectForKey:#"authKey"]]);
[[NSUserDefaults standardUserDefaults] setBool:isUserAuthenticated forKey:#"userLoggedIn"];
[[NSUserDefaults standardUserDefaults] synchronize];
if( isUserAuthenticated ){
NSLog(#"Authentificated");
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
} else {
NSLog(#"Not Authentificated");
[self showAlert];
}
}
And in the viewDidLoad method of the loginview (viewcontroller A):
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(authenticateHandler:) name:AUTHENTICATE_NOTIFICATION object:self];
If you guys could give me a sort of schema or fool-proof theory in how this is possible to do that would be great.
Because at the moment, I currently dismiss the login view with the mainview shown but then I present the tableview modally (animated), which is not desireable. So you can imagine one view being dismissed through animation then for a second you see the mainview, then the tableview is presented over the mainview through animation. I don't want the user to even see the mainview until they've logged in AND after he selects a game to view details for UNLESS the user has just one game!
And remember the only reason why the tableview is shown in between view A and C is because the user might have more than 1 game, but if they just have 1 game then go from controller A to controller C, instead of controller A to controller B to controller C.
I'm not sure I fully understand the issue, but here's some advice that I hope is useful:
A UINavigationController is especially well suited for a stack of vcs, especially, it seems to me, in the case you present.
The main vc can check the user's logged in state and either present the login vc or not. It can begin by assuming the user has more than one game to choose from and build the vc stack as follows:
- (void)viewDidAppear:(BOOL)animated {
// notice I moved the logical or to make the BOOL more meaningful
BOOL isUserLoggedIn = [[NSUserDefaults standardUserDefaults] boolForKey:#"userLoggedIn"] || (!setAuthenticationKey || [setAuthenticationKey isKindOfClass:[NSNull class]]);
if (!isUserLoggedIn) {
SelectGameVC *selectGameVC = // not sure how you build this, either alloc-init or get from storyboard
// notice animation==NO, we're putting this on the stack but not presenting it
[self.navigationController pushViewController:selectGameVC animated:NO];
LoginVC *loginVC = // alloc or use UIStoryboard instantiateViewControllerWithIdentifier
// we push this one with animation, user will see it first
[self.navigationController pushViewController:loginVC animated:YES];
}
}
When the loginVC completes login, it can ask the question, does user have more than one games? If more than one, pop back just one vc to reveal the SelectGame vc we stacked underneath, otherwise pop all the way back.
// LoginVC.m
// login successful
if (loggedInUser.games.count > 1) {
// it's underneath us, remember? we pushed it before we got here
[self.navigationController popViewControllerAnimated:YES];
} else {
// it's underneath us, but we don't need it, pop all the way back
[self.navigationController popToRootViewControllerAnimated:YES];
}
Incidentally, Your NSLog made me smile to think of my old friend who used to say "authentificated" rather than "authenticated".
I have a modal view controller which fetches a password. Since I don't want the password written to disk if my application is interrupted, I want to cear the password on applicationWillResignActive. (For those who may comment, I know the secure text field does not properly zeroize).
I've tried the following code, and my view controller is never located. For the first set of code (UIView) I believe its because I'm mixing and matching views and view controllers. I'm not sure why the second set of code (UIViewController) is not working since a few folks have suggested it.
How does one enumerate view controllers and locate a controller of interest? I simply want to send clearPassworAndPin to PasswordPromptController if present (since viewWillDisappear is not always sent when the view disappears).
Modified 7KV7 and Jhaliya answer is below (it worked). The 'if' statement using viewController.modalViewController was executed 5 times (once for each controller in the tab view). So the single modal controller of interest was sent the clearPasswords message 5 times.
for (UIViewController * viewController in viewsControllers)
{
if ([viewController isKindOfClass:passwordPromptClass])
{
[(PasswordPromptController *)viewController clearPassworAndPin];
}
else
{
if(viewController.modalViewController)
[self clearPasswords:[NSArray arrayWithObjects:viewController.modalViewController, nil]];
}
}
Using UIViews (no joy)
- (void)applicationWillResignActive:(UIApplication *)application
{
if(application.windows != nil)
[self clearPasswords:application.windows];
}
- (void)clearPasswords:(NSArray *)subviews
{
Class passwordPromptClass = [PasswordPromptController class];
for (UIView * subview in subviews)
{
if ([subview isKindOfClass:passwordPromptClass])
[(PasswordPromptController *)subview clearPassworAndPin];
}
}
Using UIViewController (no joy)
- (void)applicationWillResignActive:(UIApplication *)application
{
if(tabBarController.viewControllers != nil)
[self clearPasswords:tabBarController.viewControllers];
}
- (void)clearPasswords:(NSArray *)viewsControllers
{
Class passwordPromptClass = [PasswordPromptController class];
for (UIViewController * viewController in viewsControllers)
{
if ([viewController isKindOfClass:passwordPromptClass])
[(PasswordPromptController *)viewController clearPassworAndPin];
}
}
NSArray *array = [self.navigationController viewControllers];
yourViewController = [array objectAtIndex:yourChoiceOfIndex];
Hope it helps.
At the point where you present the PasswordPromptController as a modalViewController could you not store it as an instance variable? Then, in your applicationWillResignActive: callback you will have a handle to the VC to message against.
Be sure to release and nullify your reference to the PasswordPromptController reference when it gets dismissed.
you will have to go through the navigation stack to get the controllers.
Use UINavgationController below method to get all viewController in your navigation stack.
#property(nonatomic, copy) NSArray *viewControllers
hmm..., I have to say I like to give alternative suggestions/solutions as many folks have tried to answer you question specifically.
If you found it's not easy to find the PasswordPromptController by enumerating view controllers, you can just declare (alloc/init) that controller in your app delegate, whenever you need to use it in other controllers, get it through app delegate, do something like presenting as a modal view.
When you want to do something against it in your app delegate, e.g. clear the pwd, it's super easy because you have the reference to it.
So I have a modal view displaying in my app that has a little info for the user to fill out. The problem is that when the device is rotated, some animation occurs, but only in the frame. The form itself does not rotate. All the autorotate options are set to YES. I am displaying it when the user clicks on a field in a popover. This makes me suspect it has something to do with that but I am not sure. It is bizzare because if the device is in either view and then the modal window is displayed, it is fine. It only happens when the device is rotated in the modal view. Does anyone have any idea what may be causing this behavior when the device is rotated? Thanks!
Here is a snippet that is handled in the popover view controller:
if (currentLevel == 1 && businessOrLocation == 0){
if(tbsViewController == nil)
tbsViewController = [[BusinessFilteredViewController alloc] initWithNibName:#"BusinessFilteredView" bundle:[NSBundle mainBundle]];
NSMutableArray *tempBusiness = [[NSMutableArray alloc] init];
for (id theKey in appDelegate.groupedBusiness) {
NSMutableArray *tempArr = [appDelegate.groupedBusiness objectForKey:theKey];
[tempBusiness addObject:tempArr];
}
tbsViewController.businessOrLocation = businessOrLocation;
tbsViewController.modalPresentationStyle = UIModalPresentationFullScreen;
tbsViewController.modalTransitionStyle = UIModalPresentationFullScreen;
[self presentModalViewController:tbsViewController animated:YES];
}
I ran into this problem as well. The fundamental problem is that popover controllers cannot present modal views—it seems that case wasn’t properly considered or designed for. In my situation, it was easy enough to work around. I just extended the delegate protocol for my popover-hosted view controller. The main view sets itself up as the delegate to the popover view, and takes responsibility for displaying and dismissing the modal views the user requests from within the popover.
Since I already had a delegate protocol to cleanly dismiss the popover view when the user clicks “done” it was only a small stretch to get autorotation working the way I wanted it to. Here are some snippets:
#protocol InfoViewControllerDelegate <NSObject>
#optional
// Implement this to close the info view once the user clicks done.
- (void)infoViewDidFinish:(InfoViewController *)view;
// Implement this method if the delegate launched us as a popup view and must therefore
// take responsibility for diplaying help.
- (void)infoViewDidRequestHelp:(InfoViewController *)view;
#end
And in my main iPad view which presents this popup view:
#pragma mark - InfoViewControllerDelegate methods
- (void)infoViewDidFinish:(InfoViewController *)view {
[self hideInfo:self];
}
- (void)infoViewDidRequestHelp:(InfoViewController *)view {
[self hideInfo:self]; // Close the info view first
HelpViewController *help = [[HelpViewController alloc] init];
help.delegate = self;
[self presentModalViewController:help animated:YES];
[help release];
}
To make life simple for cases where I am launching the info view outside of a popup view (for example, on the iPhone, it is a simple modal view), it checks to see if the delegate handles the modal subviews, and if not, handles them itself. That way I didn’t need to change the iPhone base controller at all, since autorotation already worked fine there. Here’s the “Help” button action in the info view controller, showing how I did that:
- (IBAction)help:(id)sender {
if ([delegate respondsToSelector:#selector(infoViewDidRequestHelp:)]) {
[delegate infoViewDidRequestHelp:self];
} else {
HelpViewController *help = [[HelpViewController alloc] init];
help.delegate = self;
[self presentModalViewController:help animated:YES];
[help release];
}
}
With this code in place, my entire interface autorotates smoothly on both devices, whether or not popup views were involved.
Just so i understand correctly... You are displaying a popover and inside that popover if the user taps a certain element then you are displaying a full screen modal view controller? Vie never tried that before and it seems odd for two reasons.
First it seems jarring to the user in my opinion. The popover gives you a nice, integrated UI and the modal takes you away.
More importantly though, your popover view controller doesn't really have authority over the whole screen so presenting a full screen modal from a popover just seems inherently wrong.
I would suggest you display a nav controller in the popover controller and instead of presenting the new view controller modally over the whole screen just push it on to the nav controller in the popover and keep the user inside the popover.
If that doesn't really work for you, then I would suggest reviewing your UI needs and redesigning the layout.
I am guessing that you implemented - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation in BusinessFilteredViewController and returns YES
Could you check that you add more than 1 subviews to application window . If so, try to create container UIViewController for all viewControllers that you want to add to window.
Creating a new app based on the SplitViewController template and it works fine in Split View.
My main screen is to be a non-splitview 'menu'. I'm trying to figure out the best practice for
adding this 'mainMenu' modally above the splitViewController. (Then either push other non-split views above the mainMenu or
remove it to reveal and use the UISplitViewController.)
I have tried:
[self.navigationController presentModalViewController:mainMenu animated:NO];
And
[self presentModalViewController:mainMenu animated:NO];
In the viewWillAppear and viewWillLoad methods for rootViewController & detailViewController. In both cases, the code executes without error, but the mainMenu doesn't appear, the regular detailViewController and rootViewControllers appear.
(I did create an outlet from the navigationController in the main.xib file to the detailView navigationController, but that didn't change anything.)
I was able to make this work by using, which works, but seems like it is incorrect.
iPad_Prototype_SplitAppDelegate *delegate = (iPad_Prototype_SplitAppDelegate *) [ [UIApplication sharedApplication] delegate];
[delegate.splitViewController.view addSubview:mainMenu.view];
[delegate.splitViewController.view bringSubviewToFront:mainMenu.view];
I've seen many responses saying to present such a covering view modally, but I can't seem to find the right place or configuration in the splitViewController setup. Thanks for any help or insight.
Finally, is this approach wrong, should I just be swapping out the detailViewController and having it take full screen in portrait mode and not add the menu item for the root controller?
Is your splitViewController in the AppDelegate like the example and will this help?
//AppDelegate.m
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
MyController *myCont = [[MyController alloc] initMainMenu];
// mess around with myCont.view.modalPresentationStyle;
[myCont setModalDelegate:self];
// Create a delegate (<ModalControllerDelegate>) to dismiss view when done
[self.splitViewController presentModalViewController:myCont animated:NO];
[myCont release];
}
// for completion sake
-(void)modalViewDismiss:(MyController *)modalView {
[self.splitViewController dismissModalViewController:YES];
}
I have a button inside the content of a UIPopoverController. This button runs a method called myAction.
MyAction has the form
- (void) myAction:(id)sender
so, myAction receives the id of the caller button.
Now, inside this method I would like to dismiss the UIPopoverController, but the only thing I have is the ID of the caller button. Remember that the button is inside the UIPopoverController.
Is there a way to discover the ID of the UIPopoverController, given the button ID I already have?
thanks.
Unfortunately no. At least, not within the standard practices. You might be able to travel up the responder stack to find it, but it's a hack, it's buggy, and it's really, really messy.
If you want to dismiss a popover by pushing a button, some place relevant should keep a reference to the popover. Usually that would be the owner of the popover (not the controller showed within the popover). When the button is pressed, it can send a message to the owner controller, which can then dismiss the popover.
You might be tempted to have the controller displayed inside of the popover be the owner of its own popover, but coding this way is brittle, can get messy (again), and may result in retain loops so that neither ever gets released.
You can access the presenting popoverController by accessing "popoverController" with KVC.
[[self valueForKey:#"popoverController"] dismissPopoverAnimated:YES]
I have this working, and I do not think it is a hack. I have a standard split view iPad app. I then added a method on my detail controller (the owner of the pop over) to handle the dismissal.
On the standard split view architechture, both the root and detail view controllers are available via the app delegate. So I bound a button click inside the pop over to call a method which gets the app delegate. From there I call the method on the detail controller to dismiss the pop over.
This is the code for the method on the View Controller that is displayed inside the popover:
- (void) exitView: (id)sender {
MyAppDelegate *appDelegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate.detailViewController exitDrill];
}
Then the simple method to dismiss on the Detail View Controller:
- (void) exitDrill {
if(dtController != nil){
[dtController dismissPopoverAnimated: YES];
[dtController release];
}
}
I like the ability to do this because it give me a way to show a user how they can exit a pop over. This may not be necessary in future versions of the app; for right now, while this paradigm is still new to the platform, I prefer to let the users gexit a display in a couple fo different ways to make sure I minimize frustration.
As Ed Marty already wrote
If you want to dismiss a popover by pushing a button, some place relevant should keep a reference to the popover
This is very true; however, when showing a UIPopoverController, the class opening the popovercontroller keeps this resource already. So, what you could do is to use this class as the delegate class for your Popover Controller.
To do so, you could do the following, which I use in my code.
In the class opening the popover, this is my code:
- (void)showInformationForView:(Booking*)booking frame:(CGRect)rect
{
BookingDetailsViewController *bookingView = [[BookingDetailsViewController alloc] initWithStyle:UITableViewStyleGrouped booking:booking];
[bookingView setDelegate:self];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:bookingView];
self.popController = [[UIPopoverController alloc] initWithContentViewController:navController];
[self.popController setDelegate:self];
[self.popController setPopoverContentSize:CGSizeMake(320, 320)];
rect.size.width = 0;
[self.popController presentPopoverFromRect:rect inView:self.view permittedArrowDirections:UIPopoverArrowDirectionLeft animated:YES];
}
- (void)dismissPopoverAnimated:(BOOL)animated
{
[self.popController dismissPopoverAnimated:animated];
}
So what I am doing here is creating a UINavigationController and setting a BookingDetailsViewController as its rootViewController. Then I am also adding the current class as delegate to this BookingDetailsViewController.
The second thing I added is a dismissal method called dismissPopoverAnimated:animated.
In my BookingDetailsViewController.h I added the following code:
[...]
#property (nonatomic, strong) id delegate;
[...]
And in my BookingDetailsViewController.m I added this code:
[...]
#synthesize delegate = _delegate;
- (void)viewDidLoad
{
UIBarButtonItem *closeButton = [[UIBarButtonItem alloc] initWithTitle:#"Close" style:UIBarButtonItemStylePlain target:self action:#selector(closeView)];
[self.navigationItem setRightBarButtonItem:closeButton];
[super viewDidLoad];
}
- (void)closeView
{
if ([self.delegate respondsToSelector:#selector(dismissPopoverAnimated:)]) {
[self.delegate dismissPopoverAnimated:YES];
}
else {
NSLog(#"Cannot close the view, nu such dismiss method");
}
}
[...]
What happens is that when the "Close" button in the UINavigationController is pressed, the method closeView is called. This method check if the delegate responds to dismissPopoverAnimated:animated and if so, it calls it. If it does not respond to this method it will show a log message and do nothing more (so it wont crash).
I have written my code using ARC, hence there is no memory management.
I hope this helped you.