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".
Related
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
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 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.
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.
i have a 3 tabbar in my app. in my Appdelegate i have a reference to loginview where i am popingup loginview if user is not logged in.here is method.
- (void)LoginView
{
loginView = [[[LoginViewController alloc] initWithNibName:#"LoginView" bundle:nil] autorelease];
UINavigationController* nav = (UINavigationController*)[tabBarController.viewControllers objectAtIndex:0];
loginView.navC = nav; [nav presentModalViewController:loginView animated:YES];
}
3rd tabbar is a settings view and i have a signout button there.
at first time i can see correct user name,but as soon as i click sign out i am calling same method shown above using app delegate. logview gets popedup correctly and if i signin as different user it still show previous user name (because 3rd tabbar view is already loaded)
so my question is
1)which is the best place to put loginview
2)how do i reset the app w/o restarting it after signout
i hope my question is clear. or i am willing to give more details.
thanks.
Update:
basically i want to unload all view on signout and start from the beginning.
Better method would be to create a public changeLoginName: method on your settings controller, and call that method from the login view when the user is logged in. You can access that view through your tab bar, if you don't keep pointers to it anywhere else.
something which worked for me, and i hope this is proper way to doing.here is what i did.
NSArray *vc= tabBarController.viewControllers;
for (int i = 0; i < [vc count]; i++) {
UINavigationController *nc = [vc objectAtIndex:i];
if (nc == tabBarController.selectedViewController) {
continue;
}
[nc popToRootViewControllerAnimated:NO];
}
i hope this unloads all the view from memory and force them to load again when tabbar is getting switched.let me know if this is not good way.