How to manage NSUserDefaults when switching between different tabs in tab bar? - iphone

In my iPhone app, I have the unique identifier for eah project. I want that when the user logs in the unique identifier be accessible on every page.
I am using NSUserDefaults to do that. But the problem is that Login is not the first page. So if I access some page which requires unique id, then it shows "unknownkey".
How can I work around this problem?
Also, I can't add the login screen as first screen as the user can access some portions without login.

try something like
if (![[NSUserDefaults standardUserDefaults] objectForKey: #"uniqueid"]) {
//...user is not logged in..
} else {
//...user is logged in...
}

I guess that this is a design problem rather than a programming problem?
You could probably either move your login screen to the startup screen or leave the tab bar as it is until the user logs in, or even re-design the whole thing differently.

If the login is necessary for everything (e.g. your app is a dropbox client), present the login viewController modally at application start.
And don't dismiss it until you have a successful login.

In your RootViewController's viewDidLoad method, you need to check the saved NSUserDefault via an IF statement. Easy.
-(void)viewDidLoad {
if(![[NSUserDefaults standardDefaults] objectForKey:#"uniqueid"]) {
// here you'll want to present a view modally inside a new navigation controller.
LoginViewController *lvc = [[LoginViewController alloc] initWithNibName:#"LoginViewController" bundle:nil];
UINavigationController *controller = [[UINavigationController alloc] initWithRootViewController:lvc];
[self.navigationController presentModalViewController:controller animated:YES];
} else {
// this is where you'd set up the client if the user is already logged in.
}
}

Related

Successive calls PresentViewController

I am trying to add a registration page & a verification page to my app that has a 3 views that can be switched to and from using a UITabBarController. The registration page should only be shown once in this app's life time. Once a user is registered, this view will go away and be replaced by a verification page. Once a users identity is verified, a user can use the app.
Now in AppDelegate I have the following code to present the registration page to the user:
RegistrationPage *registration = [[RegistrationPage alloc] initWithNibName:#"RegistrationPage" bundle:nil];
[self.window.rootViewController presentViewController:registration animated:YES completion:nil];
After the user filled the registration page and pressed the submit button, the following code is used to dismiss the registration page and present the verification page:
VerificationPage *verification = [[VerificationPage alloc] initWithNibName:#"VerificationPage" bundle:nil];
[self dismissViewControllerAnimated:YES completion:^{
[self addVerificationPage];
}];
-(void) addVerificationPage
{
VerificationPage *verification = [[VerificationPage alloc] initWithNibName:#"VerificationPage" bundle:nil];
[self presentViewController:verification animated:YES completion:nil];
}
However the verification page just never shows up. Can someone help me with this?
I have also tried this in the registration page, does not work either:
VerificationPage *verification = [[VerificationPage alloc] initWithNibName:#"VerificationPage" bundle:nil];
[self dismissViewControllerAnimated:YES completion:^{
[[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:verification animated:YES]
}];
Most likely is an issue caused by attempting to present the VerificationPage at the same time as you dismiss the RegistrationPage. From the Apple documentation:
Dismissing a view controller dismisses not only that view controller but also any view controllers it presented.
As a result, there's two possible orders, either of which will result in failure - either the VerificationPage is created and presented, then the RegistrationPage is dismissed - taking the presented VerificationPage with it. Alternatively, the RegistrationPage is dismissed and destroyed, then the presentViewController call is sent through to it - resulting in failure as the RegistrationPage is no longer a valid ViewController.
To resolve, either leave the RegistrationPage open and present the VerificationPage over it (not ideal, but it will work), or signal the AppDelegate or a ViewController higher up the chain (you can use self.presentingViewController) to do the presenting instead.

Automatically Load UIView from another UIView

I'm trying to build an iPhone app that allows users to register from their phone itself. At the first page (login form) I have a "register now" button that displays a Registration Form to do just that.
So the workflow now is:
User stars app, load form A
App detects that user is new, UIAlert to say that User needs to register
User has to click 'Register Now' to register (form B)
I'm wondering how I'd improve the app by doing something like this:
User starts app, load form A
App detects that user is new, UIAlert to say that User needs to register
The Registration Form (form B) automagically appears.
I tried implementing some logic in Form A's viewDidLoad like:
//initWithNib:#"RegisterForm" doesn't make a diff
RegisterForm *formB = [RegisterForm new];
formB.navTitle = [NSString stringWithFormat:#"Register New"];
[self presentModalViewController:formB animated:YES];
[formB release];
But that doesn't work, so, what I'm doing wrong?
-(void)viewDidLoad {
If the user hasn't registered
{ RegisterView *view = [[RegisterView alloc] initWithNibName:nil bundle:nil];
[self presentModalViewController:view animated:YES];
[view release];
} else {
}
}
Or, if you're working with multiple views within one XIB file, define the view in the .h file, link it in the XIB file, and then in viewDidLoad, use the same if statement, but use this code instead:
[self.view bringSubviewToFront:registerView];

How come presentModalViewController is not working the second time?

I am using a tab based application that shows a presentModalViewController called "overview" that has 2 buttons on it .
In order to call it I am using the following code in app delegate:
Overview *overview = [[Overview alloc] initWithNibName:#"Overview" bundle:nil];
[self.tabBarController presentModalViewController:overview animated:YES];
When overview shows up, it has a button called that gets clicked and I am using the following code:
-(IBAction) btnLoginPressed{
[self dismissModalViewControllerAnimated:YES]; //get rid of view
Login *login = [[Login alloc] initWithNibName:#"Login" bundle:nil];
[self.tabBarController presentModalViewController:login animated:YES];
[login release];
}
However the login prsentModalViewController never shows up. Can someone explain why and what I can do to show it?
Thanks
When you present a modal view controller, you do it from the view controller currently in the view.
Assuming your second modal display of a view controller is happening in Overview.m change your code to the following:
-(IBAction) btnLoginPressed {
Login *login = [[Login alloc] initWithNibName:#"Login" bundle:nil];
[self presentModalViewController:login animated:YES];
[login release];
}
You don't need to dismiss Overview first, and in fact you shouldn't as it the animations won't work in conjunction with each other.
When you ultimately dismiss login (or however deep you want to go), you send dismissModalViewController:animated: as high up as you need to. To get back to the tab bar's controller use:
[self.tabBarController dismissModalViewController:animated]
It would be well beyond the scope of your question and the time I have to answer but you should take some time and really study the docs on implementing View Controllers. I definitely recommend following Apple's code style guidelines as one suggestion to make your code much more readable (e.g. overviewViewController vs overview). It's also clear you're just learning so keep at it.

How can I "reset" the tabbar in an iPhone application

I've an iPhone application:
When you open the app you see the "LoginView". If you login into application you see a TabBarController. In the third and last tab there is "Logout" button. If you click you see the "LoginView" again. My problem is that if you login again you see the "old" tabbar and the selected tab is the third and not the one, and there is a "Logout" button. Also, if a user login with a different user, see the old data of the previous user (very dangerous).
Here's the code:
- Delegate.h:
UITabBarController *tabBarController;
LoginViewController *loginView;
- Delegate.m (didFinishLaunchingWithOptions):
[self.window makeKeyAndVisible];
loginView = [[LoginViewController alloc] init];
if (YES) { /* if the user is not already logged */
[self.window addSubview:loginView.view];
}
Delegate.m (methods):
- (void)loginComplete {
[loginView dismissModalViewControllerAnimated:YES];
[window addSubview:tabBarController.view];
}
- (void)logoutComplete {
[[tabBarController view] removeFromSuperview];
[tabBarController release];
[window addSubview:loginView.view];
}
And here's the two methods in two different viewcontrollers:
- (IBAction)login:(id)sender {
TabNavisAppDelegate *delegate =
(TabNavisAppDelegate *) [[UIApplication sharedApplication] delegate];
[delegate loginComplete];
}
(the logout method is the same)
Guys, how can I solve this painful problem?
So, here's a list of application that do what I want: "Foursquare", "Brightkite" and others.
Each one have a login screen, a tabbar view and a logout button.
Thanks # everyone.
For login-logout-login situations where all kinds of things need to reset themselves at the logout or the next login, I like to create a notification, something like "NewUserReset." Everything that needs to reset itself to an original state listens for the notification and runs a method that does whatever kind of resetting it needs. The tabbar would change the button title to logout, temporary data structures nil/zero/release themselves, etc.
It's nicely decouples the logout from all of the things that have to be done so you're not trying to manipulate view controllers and data storage and view appearances from the the controller that received the logout tap.
Sending a notification is easy. When the user taps the Logout button you'll send out a notification like this:
[[NSNotificationCenter defaultCenter] postNotificationName:#"JMUserLogout"
object:nil];
You don't have to call it JMUserLogout, you just need a string that you'll recognize and something -- I used your initials -- to help ensure you don't accidentally send a notification that has the same name as a notification something you're unaware of is listening for.
When that notification goes out, any object that has registered with the defaultCenter to listen for #"JMUserLogout" will perform any actions you choose. Here's how your object registers (this should be located in some place like ViewWillLoad or the initialization method of the object):
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(resetForNewUser:)
name:#"JMUserLogout"
object:nil];
The selector there, resetForNewUser:, is just the name of a method you want to run when the notification goes out. That method looks like this:
- (void)resetForNewUser:(NSNotification *)notif {
// DO SOMETHING HERE
}
Where it says // DO SOMETHING HERE you'll add the code specific to your app. For example, you can add the tab bar as an observer of the JMUserLogout notification. In its resetForNewUser: method you'd change the name of the logout button to Login.
In a ViewController or View or data store that holds old data from the previous user the resetForNewUser method would delete all of that data and set things back to the way they should be fore a new user. For example, if the previous user entered data into a UITextField you would delete the text, yourTextFieldName.text = #"";
Lastly, it's important that you also remove your object as an observer before it's deallocated. In your Dealloc method of each object that registered to receive the notification you add this:
[[NSNotificationCenter defaultCenter] removeObserver:self];
Hopefully that makes sense. The Apple documentation for NSNotificationCenter explains more and they provide several sample apps that use notifications.
Seems like tabBarController is not getting released. [ retain count should be 1 before releasing] tabBarController might be retain somewhere. check the retain count of it.
If you want to reset the old data from the previous user after you log out.. all you have to do is reset the UITabBarController's viewControllers property.
So if you are subclassing UITabBarController the following code should restore your app to its original state.
self.viewControllers = #[self.viewControllerOne, self.viewControllerTwo, self.viewControllerThree];
From the documentation:
If you change the value of this property at runtime, the tab bar controller removes all of the old view controllers before installing the new ones. The tab bar items for the new view controllers are displayed immediately and are not animated into position.
The tabBarController object may have been retained somewhere. Try to remove that.
And use the following code for Login, Logout methods
- (void)loginComplete {
// initialize the tabBarController here. like the following
if(tabBarController == nil){
tabBarController = [[UITabBarController alloc] init];
}
[loginView dismissModalViewControllerAnimated:YES];
[window addSubview:tabBarController.view];
}
- (void)logoutComplete {
[[tabBarController view] removeFromSuperview];
[tabBarController release];
tabBarController = nil;
[window addSubview:loginView.view];
}
So that your problem will be solved.

Require user to go through login screen before giving access to iPhone app?

On iPhone, how do I show a login screen to get username and password before giving access to iPhone app? Also, does the iPhone store a cookie to the secure website like a web browser?
I was thinking of giving users to my website a long API key to store in the settings of their iPhone instead of asking them to login with a username/password (seems to be the Slicehost iPhone app approach.) Which is the best way to get a user to login securely? I have full control over the design of the iPhone app and website so have a lot of flexibility.
I have this need in a couple of apps, and I use the following pattern:
In the controller for the first view that gets presented to the user, I have the following test, where UserPrefsManager is a singleton that knows the user credentials. This call causes a modal view to appear (FirstTimeWelcomeViewController) which tells the person that they need to register.
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
UserPrefsManager * prefs = [UserPrefsManager sharedInstance];
if (![prefs isLoggedIn])
{
FirstTimeWelcomeViewController * vc = [[[FirstTimeWelcomeViewController alloc] initWithNibName:#"FirstTimeWelcomeViewController" bundle:nil] autorelease];
[self presentModalViewController:vc animated: false];
}
else
{
if (![[RWXLocationSingleton sharedInstance] hasLocation]) {
[[RWXLocationSingleton sharedInstance] findLocationWithAccuracy:kCLLocationAccuracyThreeKilometers withObject:self andSelector:#selector(updateLocationsView)];
}
[[self tableView ]reloadData];
}
}
FirstTimeWelcomeViewController is basically a screen with buttons that greets people and takes them to the various ways to log in:
-(IBAction) createAccount
{
UIViewController * parent = [self parentViewController];
CreateAccountViewController * vc = [[[CreateAccountViewController alloc] initWithNibName:#"CreateAccountViewController" bundle:nil] autorelease];
[self dismissModalViewControllerAnimated:false];
[parent presentModalViewController:vc animated: false];
}
imagine that these also exist for forgotPassword, and loginACcount... same pattern. This causes the current view to be replaced by a view handling the specific case that they've pressed the button for.
taking the 'loginAccount' method, you've opened the LoginAccountViewController, and it has a method called loginButton, which works something like this...
-(IBAction) loginButton
{
NSString * u = [self.username text];
NSString * p = [self.password text];
//
// app specific logic that tests various inputs and creates a user object.
//
// goes here...
//
if([user checkValid])
{
UserPrefsManager * prefs = [UserPrefsManager sharedInstance];
[prefs setPassword:p];
[prefs setUsername:u];
[self dismissModalViewControllerAnimated:FALSE];
}
//
// more app specific stuff
//
}
And that's pretty much that. You have to use one of the standard ways for putting stuff in the keychain or user defaults to save your information. check that the one you pick lasts between restores if it is something that is annoying for the user to recreate. The first part is the most useful bit, thought the rest might be useful for context.
I'd recommend looking at a technology such as OAuth (http://oauth.net/). This enables the user to authorize access to their account to an application without having to actually enter their username and password into it. Also means you dont have to worry about secure storage of the users credentials, and if their passwords change etc it doesn't matter.
It's used by facebook / twitter and a lot of other big name companies so its not going away (Its actually in the process of getting approved as an offical standard).
You can't restrict access to the app itself but it is trivial to present an initial view that ask for the username and password. If the user does not present the correct information then the app will not allow them to progress to the next view which actually loads the web site.
Likewise, it would be trivial to store a key in the app's sandboxed folders such that neither the user nor anyone else could access it save through the app.