I have a UIViewController called LoginViewController. The UIViewController has two UITextField, username and password. This is displayed as a modalViewController when the first time the app is launched and there isn't any credentials in the keychain. When the user clicks on the login button the keyboard that is presented is dismissed. The code is:
- (IBAction) loginClick: (UIButton *) sender
{
if ([username isFirstResponder]){
[username resignFirstResponder];
}
if ([password isFirstResponder]){
[password resignFirstResponder];
}
[RKObjectManager sharedManager].client.username = username.text;
[RKObjectManager sharedManager].client.password = password.text;
[progressLock lock];
[progressLock unlockWithCondition:0];
[HUD showWhileExecuting:#selector(myTask) onTarget:self withObject:nil animated:YES];
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:#"/account/verify.json" objectClass:[Login class] delegate: self];
}
Now inside the app there is an options button. When this is click it will present a modalViewController which has a logout button in it. Clicking on this logout button will present the LoginViewController again. The code is:
- (IBAction) logout:(id)sender
{
NSUserDefaults *standardDefaults = [NSUserDefaults standardUserDefaults];
NSString * username = [standardDefaults stringForKey:#"kApplicationUserNameKey"];
NSError * error = nil;
[standardDefaults removeObjectForKey:#"kApplicationUserNameKey"];
[SFHFKeychainUtils deleteItemForUsername:username andServiceName:#"convore" error:&error];
LoginViewController* lvc = [[LoginViewController alloc] init];
lvc.delegate = self;
[self.mgvc.groups removeAllObjects];
[self.mgvc.table reloadData];
Topic * topic = [Topic object];
topic.tid = [NSNumber numberWithInt:-2];
self.mgvc.detailViewController.topic = topic;
self.mgvc.detailViewController.detailItem = topic.tid;
[self presentModalViewController:lvc animated:YES];
[lvc release];
}
What's weird is that I can't seem to dismiss the keyboard when I click on the login button this time. Why is this? Is it because as of now I am displaying the LoginViewController from a modalviewcontroller? How do I solve this weird issue?
I don't know whether the fact that you can't dismiss the keyboard is due to presenting the login in a modal view, nor do I see anything evidently wrong with your code.
I would suggest you to change your flow and make:
the logout button dismiss your first modal view and,
your login view be displayed as a normal view, once the modal view disappears.
Explicitly, you can do 2 either by one of several means:
trying and pushing the login view controller on your navigation controller (if you have one);
selecting it in a tab bar controller (if you use one);
your modal view is shown above a view; when the modal is dismissed, that view appears again, and so the viewWillAppear selector of its view controller is called; that's the place where you can check if you are logged in or out and show the login, if necessary; if you don't know how to detect if your are in or out, you can always set a flag in the view controller when you dismiss the logout modal view.
(after reading your comment: modal view is not full screen, so viewWillAppear is not called: you can either send viewWillAppear yourself, or send a different, custom message to your superordinate view controller so that it knows that the login view should be displayed (and possibly refresh the view).
EDIT: try something like this (this requires that you call your own selector, not viewWillAppear):
[baseNonModalViewController performSelector:#selector(yourSelector) withObject:nil afterDelay:0.0];
Executing performSelector with a 0.0 delay, has only the effect of scheduling yourSelector for execution on the main loop. This way, first the modal will be completely dismissed, then the login view will be displayed.
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 TWO UIViewController classes, where in FirstClass i have an UIButton for Login, when user taps on button, i will display SecondClass... For that i have done,
SecondClass *index = [[SecondClass alloc] init];
[self presentModalViewController:index animated:YES];
In SecondClass i have a logout button, which will redirect to FirstClass, for that i have done,
[self dismissModalViewControllerAnimated:YES];
When i press Logout button in SecondClass, i get the warning msg
**Attempt to dismiss from view controller <FirstClass: 0e39w88e160> while a presentation or dismiss is in progress!**
What is the problem here..
Added both iOS 6 and pre-iOS 6 answers:
iOS 5.0 and later
When you logout, add this check before dismissing:
if (![self.presentedViewController isBeingDismissed])
{
[self dismissModalViewControllerAnimated:YES completion:nil];
}
iOS 4.X and less
Add this check before dismissing:
if (![[self modalViewController] isBeingDismissed])
{
[self dismissModalViewControllerAnimated:YES];
}
Call these lines where you logout & then check:
if (![[self modalViewController] isBeingDismissed])
{
[self dismissModalViewControllerAnimated:YES];
}
There are many things that may cause this, here are some options:
You forgot to call super on one of the ViewController methods such as viewWillAppear, viewWillAppear etc. Consult the UIViewController documentation to see when you have to call super.
The dismissModalViewControllerAnimated: method is being called more than once, this can happen if you added a target to the UIButton more than once.
To get better understanding of the problem please paste the code of both view controllers in its entirety.
In my app i have a login screen which i present modally on startup. After a successful login, the user gets redirected to a UITabBarController which has five UINavigationController (for the tabs).
On one tab, i have a button "logout", so that the user gets redirected back to the login screen.
That´s working fine.
But what i want to do is to load the UITabBarController every time the user logs in. That means, that the UITabBarController should NOT be reused. For now, the content in the tabs (i load data from web) stays the same, also when a new login has been done.
How can i release/pop/nil the UITabBarController with it´s UINavigationcontrollers?
What i have tried so far:
Thats the point where i push the user back to the login screen when he presses the "logout" button:
[self.navigationController presentModalViewController:navigContrLogin animated:YES];
[[self navigationController] popToRootViewControllerAnimated:YES]; --> NOT WORKING
[self.navigationController popViewControllerAnimated:NO]; --> NOT WORKING
[self.tabBarController release]; ---> NOT WORKING
Can anybody help me please?
EDIT:
That´s how i add the UITabBarController. I do this when the user clicks the Login Button:
[self.navigationController dismissModalViewControllerAnimated:NO];
[self.navigationController setNavigationBarHidden:YES];
[self.navigationController pushViewController:tabBarController animated:NO];
[self.navigationController removeFromParentViewController];
EDIT2: I solved it. What i´m doing is when the user clicks the logout button, i call the navigation controller from the app delegate and use it to push the login viewcontroller.
MyAppDelegate *del = (MyAppDelegate*)[UIApplication sharedApplication].delegate;
[del.navControllerLogin pushViewController:loginController animated:YES];
Have you tried to remove it from the superview and then release it? And then add new ViewControllers?
for (UIView *view in self.window.subviews){
if (view == tabBarController.view) {
[view removeFromSuperview];
}
}
[tabBarController release];
UITabBarController *newTabBarController = [[UITabBarController alloc] init];
newTabBarController.viewControllers = nil; //ADD NEW VIEWCONTROLLERS
[self.window addSubview:newTabBarController.view];
i wouldn't do it with this way. Because managing (release/nil) a parent view from its subview is not a good practice.
init and show tabbar controller after app launch.
if user is not logged on, present loginviewcontroller. if already logged on no need.
after successfull login post a notification and capture it required places.
if user taps on logout, clear user credentials, user related data and present loginviewcontroller again.
I'd add and remove both the modal view controller and tabbarcontroller from the appDelegate.
[myAppDelegate addLoginViewController];
[myAppDelegate removeLoginViewController];
[myAppDelegate addTabBarController];
[myAppDelegate removeTabBarController];
I have an application that pulls up a login page when it first starts. This login page goes over the application and does not let anyone through until they've logged in. I also have a settings tab on my main application that needs to lead back to this login screen. Right now it displays the login screen with the tab bar over it. Is there a way to get the login view over the tab bar?
I've done something similar by having views transitioning in over the top of my tab bar. I used yourView.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal; with a 'close' button to let users return to the tab bar screen. I've not done this so it automatically comes in on app fire-up but instead call the view from a button on the screen separate to the tab bar controls. However, I'm sure you'll be able to utilise this somehow to do what you want.
In fact I've actually used this way of calling up views all over my app, each time it covers the tababr and you have to 'close' it to get back to main tabbed navigation you came from.
Try setting below in your viewDidLoad of login screen:
self.tabBarController.hidesBottomBarWhenPushed = YES;
You can do this by using a subclass of UITabBarController, which then performs various checks in viewDidAppear:. The login view is modally presented, as #Maxwell suggested.
// a subclass of UITabBarController
- (void) viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self firstLoadChecks];
}
- (void) firstLoadChecks
{
if (!self.hasLogin) {
id login = [[[LoginViewController alloc] initWithDelegate:self autorelease];
id nav = [[[UINavigationController alloc] initWithRootViewController:login] autorelease];
nav.modalPresentationStyle = UIModalPresentationStyleFormSheet;
nav.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentModalViewController:nav animated:YES];
}
}
// from LoginViewControllerDelegate
- (void) didLogin
{
self.hasLogin = YES;
[self dismissModalViewControllerAnimated:YES];
}
// my LoginViewController can be closed without a login
- (void) dismissModalViewControllerAnimated:(BOOL) animated
{
[super dismissModalViewControllerAnimated:animated];
[self firstLoadChecks];
}
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.