Correct way to have a one time login iPhone? - iphone

I'm trying to create an app that will require a login upon first time load. Once the user logged in successfully the credentials will be stored in a keychain so the user doesn't have to keep logging in.
Here is a quick mockup of the following code I've written; Is using the AppDelegate the correct way to go about handling which View to show first?
AppDelegate.h
#import <UIKit/UIKit.h>
#import "KeychainItemWrapper.h"
#import "ViewController.h"
#import "TestBViewController.h"
#import "User.h"
#interface AppDelegate : UIResponder <UIApplicationDelegate>
#property (strong, nonatomic) UIWindow *window;
#property (strong, nonatomic) TestBViewController *mainVC; // User see's after loggin in
#property (strong, nonatomic) User *user;
#property (strong, nonatomic) KeychainItemWrapper *keychainItem;
- (void)saveKeychainUsername:(NSString *)username andPassword:(NSString *)password;
- (void)loadLoggedInViewControllers;
#end
AppDelegate.m
#import "AppDelegate.h"
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.keychainItem = [[KeychainItemWrapper alloc] initWithIdentifier:#"AppUniqueID" accessGroup:nil];
// [self.keychainItem resetKeychainItem];
UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
// Override point for customization after application launch.
NSString *password = [self.keychainItem objectForKey:(__bridge id)(kSecValueData)];
NSString *username = [self.keychainItem objectForKey:(__bridge id)(kSecAttrAccount)];
NSLog(#"username: %#, password: %#", username, password);
if ([username length] <= 0 || [password length] <= 0) {
// Login VC
ViewController *loginVC = (ViewController *)[mainStoryboard instantiateViewControllerWithIdentifier:#"Login"];
self.window.rootViewController = loginVC;
} else {
[self loadLoggedInViewControllers];
}
return YES;
}
- (void)loadLoggedInViewControllers {
UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
// Attempt to login user
self.user = [[User alloc] init];
self.user.name = #"Bob";
self.user.hasAccess = YES;
// If user is no longer valid (for whatever reason) remove his keychain information so we can save the new ones.
// Valid user! Skip Login VC
self.mainVC = (TestBViewController *)[mainStoryboard instantiateViewControllerWithIdentifier:#"Main"];
self.mainVC.user = self.user;
self.window.rootViewController = self.mainVC;
}
ViewController.m - Think of this as my LoginViewController
- (IBAction)storePassButtonTap:(id)sender {
if ([[username text] length] > 0 && [[password text] length] > 0) {
AppDelegate *shareDelegate = [[UIApplication sharedApplication] delegate];
[shareDelegate saveKeychainUsername:[username text] andPassword:[password text]];
[shareDelegate loadLoggedInViewControllers];
} else {
if ([[username text] length] <= 0) {
// Error message
}
if ([[password text] length] <= 0) {
// Error message
}
}
}

I wont recommend doing this. I have done this in the past and I have faced problems such as the orientation delegates were not working once I change the rootviewcontroller property of appdelegate like this. Looks like it is fixed in iOS 6, but my understanding is that this is something unusual and not supposed to do.
Instead have a dummy viewcontroller as rootviewcontroller and add other viewcontrollers as its childViewController. You can remove and add any view controller this way.

Personally I wouldn't. I use storyboards and would set a view controller as the root. If I need to do this I would leave the view controller blank (maybe have an activity indicator if it takes a minute to login), run the login code here and then either move to the login segue or the main app segue.
Making sure that both the next screens hide the back button so the user can't go back to this screen.
I like the leave the appDeleage for things like reestablishing tcp connections and closing them when entering background.

Related

Trying to dismiss a view controller by tap on cancel using delegation (Not working at all)

Having two view controller within a navigation controller:
PhoneNumbersTVC > holds a list of phone numbers added by NewPhoneNumberTVC
NewPhoneNumberTVC > a controller for adding phone numbers
When I tap on cancel on NewPhoneNumberTVC I like to get back to PhoneNumbersTVC with a delegation as follow.
PhoneNumbersTVC.h
#import <UIKit/UIKit.h>
#import "NewPhoneNumberTVC.h"
#interface PhoneNumbersTVC : UITableViewController <NewPhoneNumberTVCDelegate>
#end
PhoneNumbersTVC.m
- (void)saveBtnWasTappedOnNewPhoneNumberTVC:(NewPhoneNumberTVC *)newPhoneNumberTVC
{
NSLog(#"saveBtnWasTappedOnNewPhoneNumberTVC");
[newPhoneNumberTVC.navigationController popViewControllerAnimated:YES];
}
- (void)cancelBtnWasTappedOnNewPhoneNumberTVC:(NewPhoneNumberTVC *)newPhoneNumberTVC
{
NSLog(#"cancelBtnWasTappedOnNewPhoneNumberTVC");
[newPhoneNumberTVC.navigationController popViewControllerAnimated:YES];
}
NewPhoneNumberTVC.h
#import <UIKit/UIKit.h>
#class NewPhoneNumberTVC;
#protocol NewPhoneNumberTVCDelegate <NSObject>
- (void)saveBtnWasTappedOnNewPhoneNumberTVC:(NewPhoneNumberTVC *)newPhoneNumberTVC;
- (void)cancelBtnWasTappedOnNewPhoneNumberTVC:(NewPhoneNumberTVC *)newPhoneNumberTVC;
#end
#interface NewPhoneNumberTVC : UITableViewController
#property (weak, nonatomic) id <NewPhoneNumberTVCDelegate> delegate;
#property (strong, nonatomic) NSManagedObjectContext *managedOC;
#property (weak, nonatomic) IBOutlet UITextField *phoneNumberTextField;
- (IBAction)saveBtnTapped:(UIBarButtonItem *)sender;
- (IBAction)cancelBtnTapped:(UIBarButtonItem *)sender;
#end
NewPhoneNumberTVC.m
- (IBAction)cancelBtnTapped:(UIBarButtonItem *)sender
{
NSLog(#"cancelBtnTapped");
self.phoneNumberTextField.text = #"";
self.phoneKindTextField.text = #"";
[self.delegate cancelBtnWasTappedOnNewPhoneNumberTVC:self];
}
When I tap on cancel on NewPhoneNumberTVC I see that above method cancelBtnTapped fires but delegation does not work, no method executes in PhoneNumbersTVC. View doesn't go away and I don't see: cancelBtnWasTappedOnNewPhoneNumberTVC on console.
If you want this method cancelBtnWasTappedOnNewPhoneNumberTVC: get called, you definitely need to set the delegate of your NewPhoneNumberTVC object to a PhoneNumbersTVC object. For example, there should be existing some codes in PhoneNumbersTVC.m :
NewPhoneNumberTVC *myNewPhoneNumberTVC = [[NewPhoneNumberTVC alloc] init];
myNewPhoneNumberTVC.delegate = self;
You can log as following to verify that the delegate is set successfully or not"
NewPhoneNumberTVC.m
- (IBAction)cancelBtnTapped:(UIBarButtonItem *)sender
{
NSLog(#"cancelBtnTapped");
self.phoneNumberTextField.text = #"";
self.phoneKindTextField.text = #"";
NSLog(#"self.delegate :%#",self.delegate);
[self.delegate cancelBtnWasTappedOnNewPhoneNumberTVC:self];
}

In App Purchases in iPhone Programming

I have developed an application with in App Purchases. I have the following code... In the ViewController.m file I the butoon1Clicked Method I am callInAppPurchases. Actually when click on the button first time i want to perform In App Purchases. If the Transaction is Successful, from the next Click I want to Perform Some Action (For Example I gave NSLog Statement) Where Should I have to write the NSLog stmt(or any code) in the program to perform the Action when i click on the button after A successful Transaction. Please Exaplain me where i have to write. Or Explain me in any process. I thought i want to handle with BOOL Values but i am not able to know where i have to set the BOOL Value as YES / NO. Please Explain me... MyStoreObserver.m file do the In App Purchase Transactions.
ViewController.m
-(IBAction)button1Clicked:(id)sender
{
[self callInAppPurchase];
NSLoG(#"Perform Some Action");
}
#pragma mark-In-AppPurchase code from here
-(void)callInAppPurchase
{
if ([SKPaymentQueue canMakePayments])
{
// Display a store to the user.
}
}
AppDelegate.h
#import <UIKit/UIKit.h>
#import "MyStoreAbserver.h"
#interface iTeach_MathsAppDelegate : NSObject <UIApplicationDelegate>
{
BOOL isPurchased,isFailed;
}
#property (nonatomic, readwrite) BOOL isPurchased,isFailed;
#property (nonatomic, retain) IBOutlet UIWindow *window;
AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions: (NSDictionary *)launchOptions
{
MyStoreAbserver *observer = [[MyStoreAbserver alloc] init];
[[SKPaymentQueue defaultQueue] addTransactionObserver:observer];
if([[[NSUserDefaults standardUserDefaults] objectForKey:#"isPurchased"] isEqualToString:#"true"])
self.isPurchased = YES;
else
isPurchased = NO;
isFailed = NO;
// Override point for customization after application launch.
[self.window makeKeyAndVisible];
return YES;
}
I think you refer this link, and implement code... This link is useful for me...
When product purchase you call this function:
(void)productPurchased:(NSNotification *)notification

Trying to pass data between viewControllers

I have a sequence of 4 viewControllers inside a NavigationController, each grabs a few textFields of input from the user which are stored in a NSMutableDictionary.
Each of the VC's set's itself up as the delegate of the nextVC before it segues, it also passes the NSMutDict along.
This works fine.
What I don't understand is this:
Say I have filled in the 5 textFields in VC1. Then I set myself as the delegate of VC2, pass VC2 the dictionary with the input data and segue to VC2. In VC2 I fill in another 4 textFields and add these to the dictionary. If I then decide I need to change something in VC1 I tap the back button and amend the data. But when I go forwards again I lose the stuff I input on VC2.
How do I pass the dictionary back to VC1 with the added info so that when it gets passed forwards to VC2 again it has everything in it?
The delegate (VC1) has a method to update its dictionary with the dictionary in VC2.
I have also customised the backBarButtonItem in VC2 by setting it in the prepareForSegue: method in VC1.
I think I'm getting close but...
I can only get the target actions to work by setting a leftBarButtonItem in VC2 and using that instead of the default back button.
Setting the back button in VC1 (prepareForSegue:) doesn't seem to allow any target or action to be set.
I know I can't set the back button in VC2, so what can I do? Can I set the target and action of the back button from VC2 using the delegate?
I think it may be something to do with UINavigationBarDelegate but I can't figure out where to put what with that. I tried setting it up in VC2 but it didn't do anything.
TIA.
Here's the relevant code:
Protocol:
#import <Foundation/Foundation.h>
#protocol IAXAddNewUserDelegate <NSObject>
#required
- (void)updateNewUserDataWithData: (NSMutableDictionary *)newData;
#end
From VC1.h:
#import "IAXAddNewUserDelegate.h"
#interface IAXAddNewUser1 : UITableViewController <UITextFieldDelegate, UIAlertViewDelegate, IAXAddNewUserDelegate>
#property (strong, nonatomic) NSManagedObjectContext *managedObjectContext;
#property (strong, nonatomic) User *selectedUser;
#property (strong, nonatomic) User *aNewUser;
#property BOOL isFirstUser;
- (void)updateNewUserDataWithData: (NSMutableDictionary *)newData;
#end
From VC1.m:
#pragma mark - Segues
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"AddUser2"]) {
IAXAddNewUser2 *addUser2VC = segue.destinationViewController;
addUser2VC.managedObjectContext = self.managedObjectContext;
addUser2VC.progressTotal = self.progressTotal;
addUser2VC.isFirstUser = self.isFirstUser;
addUser2VC.userData = self.userData;
addUser2VC.delegate = self;
if (self.selectedUser) {
addUser2VC.selectedUser = self.selectedUser;
}
self.title = #"Step 1";
UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithTitle:#"Back"
style:UIBarButtonItemStyleBordered
target:self
action:#selector(passDataBack:)];
self.navigationItem.backBarButtonItem = backButton;
}
}
#pragma mark - IAXAddNewUserDelegate Methods
- (void)updateNewUserDataWithData: (NSMutableDictionary *)newData
{
self.userData = newData;
NSLog(#"Updated AddUserVC1");
}
From VC2.m
-(void)passDataBack:(id)sender
{
NSLog(#"Sending Data Back to VC1");
[self.delegate updateNewUserDataWithData:self.userData];
[self.navigationController popViewControllerAnimated:YES];
}
If you're updating all the dictionaries from all the other dictionaries, try using a singleton. You can see an example here: https://stackoverflow.com/a/9690731/542400
Also, here's some code:
MainDictionary.h
#interface MainDictionary : NSObject{
NSMutableDictionary *dictionary;
}
+(MainDictionary *)sharedDictionary;
-(NSString *)getStringForKey:(NSString *)string;
-(void)setString:(NSString *)string forKey:(NSString *)key;
#end
MainDictionary.m
#import "MainDictionary.h"
static MainDictionary *sharedDictionary;
#implementation MainDictionary
-(id)init{
self = [super init];
dictionary = [[NSMutableDictionary alloc] init];
// if you want to add anything preliminary to the dictionary, do it here
return self;
}
+(MainDictionary *)sharedDictionary{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedDictionary = [[self alloc] init];
});
return sharedDictionary;
}
-(NSString *)getStringForKey:(NSString *)string{
return [dictionary objectForKey:string];
}
-(void)setString:(NSString *)string forKey:(NSString *)key{
[dictionary setValue:string forKey:key];
}
#end
Now #import MainDictionary.h, and any time you want to access or set values in that dictionary (in this example, when your textFields end editing), just do this:
-(void)textFieldDidEndEditing:(UITextField *)textField{
if(textField == textField1){
[[MainDictionary sharedDictionary] setString: textField.text forKey:#"textField1"];
}
}
or:
-(void)viewWillAppear{
textField1.text = [[MainDictionary sharedDictionary] getStringForKey:#"textField1"];
[super viewWillAppear:YES];
}
Implement this in each VC, and you're good to go.

How to properly create a root view controller?

After upgrading to xCode 4.2 I am getting the following warning...
Applications are expected to have a root view controller at the end of application launch
After reading as much as I could find on line about the RootViewController I am not sure whether I have created my root view controller properly. I created it a long time ago when I was first learning to program in xCode.
One question I have is it ok to name the root view controller something other than RootViewController. Every example I see now has it named RootViewController. I also see it synthesized in the app delegate like this...
#synthesize rootViewController = _rootViewController;
I do not understand what this is doing. Why not just...
#synthesize rootViewController;
In any event I changed the name of my root view controller to RootViewController and followed the example I found at cupsofcocoa.com. But even after the changes I am still getting the "...expected to have a root controller..." warning.
If someone has the time to take a look and let me know what I am missing, I have listed the the significant portions of my initialization code below.
Thanks,
John
//RootViewController.h
#import <UIKit/UIKit.h>
#interface RootViewController : UIViewController {
}
#end
.
//RootViewController.m
#import "RootViewController.h"
#import "JetLoggerAppDelegate.h"
#implementation RootViewController
#end
.
//JetLoggerAppDelegate.h my app delegate
#import <UIKit/UIKit.h>
#class RootViewController;
#interface JetLoggerAppDelegate : NSObject <UIApplicationDelegate> {
UIWindow *window;
RootViewController *rootViewController;
}
#property (nonatomic, retain) IBOutlet UIWindow *window;
#property (nonatomic, retain) IBOutlet RootViewController *rootViewController;
#end
.
//.m app delegate
#import "JetLoggerAppDelegate.h"
#import "RootViewController.h" //I don't think I need this here
#implementation JetLoggerAppDelegate
#synthesize window;
#synthesize rootViewController = _rootViewController;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
if ([launchOptions count] == 0) {
_rootViewController = [[RootViewController alloc] init];
self.window.rootViewController = self.rootViewController;
[window makeKeyAndVisible];
return YES;
}else{
[JLHelper showAlertWithTitle:#"" message:[NSString stringWithFormat:#"launchOptions: %#", launchOptions]];
}
return NO;
}
.
//main.m
#import <UIKit/UIKit.h>
int main(int argc, char *argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
int retVal = UIApplicationMain(argc, argv, nil, #"JetLoggerAppDelegate");
[pool release];
return retVal;
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
if ([launchOptions count] == 0) {
_rootViewController = [[RootViewController alloc] init];
self.window.rootViewController = self.rootViewController;
**[window addSubview:_rootViewController.view];**
[window makeKeyAndVisible];
return YES;
}else{
[JLHelper showAlertWithTitle:#"" message:[NSString stringWithFormat:#"launchOptions: %#", launchOptions]];
return NO;
}
return nil;
}
Put return NO inside else statement and on the end put return nil; Hope this help.
Applications are expected to have a root view controller
Replace in AppDelegate
[window addSubview:[someController view]];
to
[self.window setRootViewController:someController];

Load different initial view based on application preferences?

I have a preference that, when set, forces my application to perform some synchronization on startup.
Can I use IB to display a different initial view based on this setting?
Is there a standard way to enable this behavior?
Assuming you have a property on your app delegate that is set during your synchronization, in the initial view controller's initWithNibNamed: method check the value synced by the app delegate and load the appropriate nib by calling [super initWithNibNamed:#"thisNibInsteadOfThatNib"];
EDIT: Show code to launch a different view depending on some condition at launch
// AppDelegate.h
#import <UIKit/UIKit.h>
#interface AppDelegate : NSObject <UIApplicationDelegate>
{
UIWindow *window;
UIViewController *firstViewController;
}
#property {nonatomic, retain} UIWindow *window;
#end
// AppDelegate.m
#import AppDelegate.h
#import ViewControllerOne.h
#import ViewControllerTwo.h
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
BOOL shouldLoadViewOne = \\ some value from preferences
if (shouldLoadViewOne) {
firstViewController = [[ViewOneController alloc] initWithNibName:#"ViewOneController" bundle:nil];
} else {
firstViewController = [[ViewTwoController alloc] initWithNibName:#"ViewTwoController" bundle:nil];
}
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:firstViewController];
[window addSubView:[navController view]];
[window makeKeyAndVisible];
return YES;
}
EDIT 2:
Make use of NSClassFromSting() and save the name of the firstViewController to load in the preferences.
// AppDelegate.h
#import <UIKit/UIKit.h>
#interface AppDelegate : NSObject <UIApplicationDelegate>
{
UIWindow *window;
id firstViewController;
}
#property {nonatomic, retain} UIWindow *window;
- (NSString *)firstViewControllerName;
#end
// AppDelegate.m
#import AppDelegate.h
#import ViewControllerOne.h
#import ViewControllerTwo.h
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSString *viewControllerName = [self firstViewControllerName];
firstViewController = [[NSClassFromString(viewControllerName) alloc] initWithNibName:viewControllerName bundle:nil];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:firstViewController];
[window addSubView:[navController view]];
[window makeKeyAndVisible];
return YES;
}
- (NSString *)firstViewControllerName
{
NSString *defaultViewController = #"ViewOneController";
NSString *savedFirstViewController = // string retrieved from preferences or other persistent store
if (!savedFirstViewController)
return defaultViewController;
return savedFirstViewController;
}