In my client application I have an idle timeout control mechanism and when the user does not do anything with the app for a specified time interval, I display a warning and throw him back to the login screen. This control happens in my container view where I initiate all of my other views. When the idle time is up, I pop this container view to its caller, i.e the login screen.
The problem is, if the user does sthg that displays an action sheet or a popover and then does not do anything until the idle time is up, when I throw him to the login screen the action sheets and the popovers also remain on the login screen since I don't dismiss them.
To solve this, I can think of making all the action sheets and the popovers retained members of my view controllers and then dismissing them on the viewWillDisappear methods of their owners. But I have so many view controllers, so I'm looking for other ways, if there are any.
So, the question is how can I make all these action sheets and popovers go away from my login screen without knowing who their callers are?
I would register the UIPopover instance to listen to some notification.
[[NSNotificationCenter defaultCenter] addObserver:_myPopOver
selector:#selector(myDismissPopover)
name:#"dismissPopover"
object:nil];
And add extension to UIPopover class.
- (void) myDismissPopover {
[self dismissPopoverAnimated:YES];
}
When I need to dismiss popover, I just need to post notification.
[[NSNotificationCenter defaultCenter] postNotificationName:#"dismissPopover"
object:nil];
I'll write down my own solution as we've talked with bshirley in the comments of the question.
I've implemented a mechanism like this to solve the problem:
In my login view controller, I create an NSMutableArray that will keep all my action sheets and popover controllers that are going to be dismissed. Then I store this array in a global dictionary. I access this dictionary via a utility method. Then all through the application, whoever creates an action sheet or a popover controller, adds the component to this array (retrieves the array from the global data, modifies it and then saves it back to the global data). Then when the user is thrown back to the login screen, in viewWillDisappear of my login view controller, I loop through this array and call the appropriate dismiss method by checking if the UIView I get from the array is an action sheet or a popover controller. Then I remove all the elements of this array and then store it back in the global data, again.
Hope this helps anyone who needs to implement a similar mechanism. Your comments will be appreciated.
Related
I have an iPad app that uses a UIPopover from within a UIVIew; I need to show an alert-type message when a certain condition has been met.
The problem is using a UIAlertView from within the UIPopover, when the user taps on a button in the UIAlertView, it also dismisses the UIPopover, which defeats the purpose of the alert.
I tried using UIActionSheets, but they don't display at all, probably because they are not being called from a controller-type view.
Is there a way to circumvent this behavior?
No, and you shouldn't do that. Popovers are supposed to go away as soon as you touch anything else.
You could enlarge the popover slightly and make room for a status message. When the user creates an appointment that overlaps, you could display a message in the status area.
Or, you could dismiss the popover and display an alert with "ok"/"cancel" buttons. The OK button would create the overlapping appointment, and the cancel button would discard it.
You will need some place to save the info from the popover while you are waiting for the user to decide what to do with the alert. Perhaps have the popover pass a message back to the view controller it comes from, and then have the source view controller create the alert, set itself as delegate, and handle the responses from the user.
According to Apple's Human Interface Guidelines, it is OK to display a UIAlertView on top of a popover:
https://developer.apple.com/library/ios/documentation/userexperience/conceptual/MobileHIG/Alerts.html
To quote specifically:
On iPad, don’t display a modal view on top of a popover. With the
possible exception of an alert, nothing should display on top of a
popover.
Displaying a UIAlertView from a popover does not automatically dismiss the popover. There is likely some of your own code being executed which is causing it to dismiss. For example, in a similar situation I had, I found that displaying a UIAlertView was invoking "shouldAutorotate" in my split view controller, and (due to earlier iOS bugs) I had placed code there to dismiss the popover. For iOS7+ this was no longer necessary, so I was able to move this code into willRotateToInterfaceOrientation, where it no longer causes dismissal of the popover upon display of UIAlertView, because in this case, even though "autoRotate" gets called "willRotateToInterfaceOrientation" does not.
I have an app I am working on that has a main screen with two buttons. One will take you to a view of a GPS (map) and then once there (new VC) it has options for setting that position or bringing up a list (tableview, another VC) of all locations already tagged.
At the list VC, if you click on the table cell, it will bring up the VC with the map. Problem is, this then adds the same VC bak on the stack. If a user clicks the Cancel button, they go back ones screen, then cancel goes back another screen, etc... until back to the main.
I know I can do the [self.navigationController popToRootViewControllerAnimated:YES]; to pop back to root but that is not always what I want.
Also, I know I can do: [[[self presentingViewController] presentingViewController] dismissModalViewControllerAnimated:YES];
I guess what I am saying is I want to "reuse" the GPS map view so I can call it from other VC's, so that is why I didn't go with the "pass back" to calling VC. So, is there away to either when a button is pressed and is to present a new VC, can I dismiss the prior one after the new one is shown? This way, a dismiss of current VC would take me back to where I need to be.
I hope makes sense and also that this question doesn't fall into the "Not an actual question" category.
Any help or better suggestions is greatly appreciated. Thx
Geo...
If you want to jump back some number of levels in a navigation controller's VC stack, you'll probably want to use its popToViewController:animated: method. To figure out if a particular view controller is on that stack, look at the navigation controller's viewControllers property. Be careful, though, as this kind of jumping around is a rather nonstandard UI behavior (even though there's API for it) which might confuse your users.
Also, using navigation controllers and presenting modally aren't the only ways to manage multiple view controllers -- you can always set the window's rootViewController yourself (and animate the change with UIView animations), even wrapping up your custom transition type in a custom UIStoryboardSegue if you like.
You can put a delegate in the table view. So that when a cell is pressed the info is passed to the delegate method in the VC which will dismiss the table view and reloads itself with the new info. You will have to implement refresh method in that VC.
Okay, so I'm building an universal iOS app with an initial login view (view controller named LoginVC), just a plain simple UIViewController. If the login is successful the app segues to an navigation controller (MainNavigationVC). I created this segue through the storyboard gui of XCode, so no programmatic creation of the nav controller is done. The nav controller is presented modally in fullscreen, so the rest of the app is run atop the login view, with this nav controller as the centerpiece of everything.
The navigation controller contains a view (with a view controller named UserStartPageVC), and in its navigation bar is a logout button. This button sends an target action to UserStartPageVC, with the goal of dismissing the nav controller thus bringing the user back to the login view.
So far everything works fine. I can login and use the app as intended. But! When I log out and then re-login XCode tells me this:
Warning! Attempt to present <MainNavigationVC: 0x753110> on
<LoginVC: 0x756fcf0> while a presentation is in progress!
I suppose this means that the login view is trying to modally display a MainNavigationVC navigation controller, but another one is already displayed, right? But how? Can a view be presented without showing?
And how can I get rid of the old nav controller when logging out? I've tried several ways of dismissing the modal view, for instance:
from within UserStartpageVC running
[x dismissViewControllerAnimated:YES completion:NULL]
[x dismissModalViewControllerAnimated:YES]
where x is either self, self.parentViewController or self.presentingViewController.
setting the LoginVC as a property in UserStartpageVC and running
[self.loginVC dismissViewControllerAnimated:YES completion:NULL]
and so on.
All of the tested calls actually brings me back to the login screen, so it's kind of working.
Any ideas? Relevant code samples can be provided if necessary, I just couldn't figure out which pieces that were of interest. The seguing to the navigation controller has no code (except for a performSegueWithIdentifier:sender:), and the code for dismissing it is the part I cannot seem to get straight.
As a sidenote. So far this isn't a REAL problem; the app runs, and it IS possible to logout and re-login without any other side-effects than an error message in XCode. But I suppose this will be a memory leak if users logout and login multiple times, and I'm not in the mood of an unnecessary rejection from Apple.
I discovered another way to get the exact same error message. Lucky me!
If you created a segue at one point and had it tied to a button (click button -> new view) and then later give that segue a name and invoke it directly using
[self performSegueWithIdentifier:#"identifierName" sender:self];
then you can get this error because you can effectively trigger the segue twice. I thought making the button invoke an IBAction would turn off the segue I had set up in the first place, but apparently not. Hitting the button triggered the segue twice, but after I deleted the segue and re-created it as a manual segue on the view with the same identifier then I was able to invoke it via the above code and not get the warning message.
Hoopla! My bad.
Seemed I had set up the notification observing from the login API call in a stupid way. For every time the user triggered a login (or re-login), it added itself as an observer for the notification; the result was that it performed one more segue for every time a login was done.
And doing multiple segues at the same time, is... well, obviously bad.
Hi there and thank you in advice for your help. I have a really strange problem while working with ViewControllers in Xcode4. First of all I have to say that I'm not using storyboards and I prefer to create any UI element programmatically. So I've set a UIButton and I want that, when pressed, it brings me to a new view controller. This is the code I'm using for a button:
-(void)settingsAndExportHandle:(UIButton *)buttonSender {
SettingsViewController* settingView = [[SettingsViewController alloc] initWithNibName:#"SettingsViewController" bundle:nil];
settingView.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:settingView animated:YES];
}
This buttons is initialized and allocated in the viewDidLoad method of the RootViewController. I want to switch to the other view controller (in this case SettingsViewController) when I press the button.
The strange thing is that when I press the button, the animation that flips the controllers goes well, but when it finishes I obtain the EXACT same things that I had on the RootViewControllers (same custom views, same buttons, same all!). The question is: what I'm missing?? I have to say that I use ARC (automatic reference counting) so I can't release or dealloc the views and buttons I've created on my RootViewController.
Any help will be appreciated. Thank you all!
Pushing and and modally presenting view controllers does not deallocate the view controller that presented them. It simply adds the additional view controller to the stack. You'll need to implement a callback method so that when the user hits the button to flip back to root view controller, your settings view controller lets the root view controller know what's about to happen so you can call a method you've written to reset the interface back to whatever state you need it at. You may also be able to use viewWillAppear: but that's a little messy.
However, according to the Apple Human Interface Guidelines, the user expects that when they push a view controller or modally present it, the view controller they were on will save state and be exactly the way they left it when they came back. It's disconcerting and annoying when state is not preserved while in a navigation controller context. It is especially annoying when it's modally presented.
Think about this - A user is in a hypothetical Mail app. They start typing out an email and set a font size and a color. They tap on the add attachment button, which brings up a modal view controller that allows them to select a picture. They select the picture and the modal view is dismissed, and in your implementation, the mail composing interface would have reset and the email content would be gone or at the very least the selected font size and color would be back to the default. That's not a good experience.
In viewDidDisappear: of a modally presented controller I added a callback to inform who ever is interested about the view being gone (after the animation has finished) without requiring subclassing.
One of my controllers that registered for the callback is firing up a UIAlertView in there. However, once the alert is shown, its buttons don't react.
Another one is adding a subview to itself and again: the buttons of the view don't react.
The resposible handlers of the buttons are not triggered.
I assume it has to do with the fact that viewDidDisappear: is not really finished yet when it call my callback. But even if I used subclassing instead, it would be the same situation.
One explanation could be that there is still some other view covering my buttons because the clicks just don't come through.
So: Can somebody confirm that it is NOT a good idea to do what I am doing (showing an alert, adding a subview in viewDidDisappear), because then I will have to change the flow. If it should be okay, I have to figure out what else is causing this effect.
I'd put the callback into viewWillDisappear: instead. At least then the original UIView reference is still around.
Better solution would be to dismiss the modal view through the parent controller by adding the caller as a delegate. The delegate would implement a protocol to dismiss the modal controller. Call the delegate protocol from the modal view, when you are ready to dismiss.
To avoid changing the flow, you could schedule a timer to show the alert, this will give a chance to the view controller code to complete