going back to AppDelegate to recreate a uitabbarcontroller - objective-c

I have an app that is based on a tab bar view with a welcome screen (that leads to either signin or sign up process). basically, if you are logged in - you go straight to the tabbar view and if not, you go to the welcome screen, where you can chose to either go to sign in or sign up. assuming that you go to either sign in or sign up, i would like the tab bar view to reappear, however, all the declarations are in the AppDelegate. how can I "go back" and call the tabbatcontroller? is the structure / flow of my classes correct at all?
so to repeat:
user signed in -> first view is tab bar view
user logged out -> welcome screen view --> sign in / up screen view --> tab bar view
what i am looking for is what do i need to write in this action method that is called once the user clicks on "sign in" in the sign in page:
-(IBAction)done:(id)sender {
?????
}
for reference, my appDelegate is:
if(user signed in)
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
UIViewController *viewController1 = [[FirstTab alloc] initWithNibName:#"FirstTab" bundle:NSBundle.mainBundle];
UIViewController *viewController2 = [[SecondTab alloc] initWithNibName:#"SecondTab" bundle:NSBundle.mainBundle];
UINavigationController *secondNavController = [[UINavigationController alloc]initWithRootViewController:viewController2];
self.tabBarController = [[UITabBarController alloc] init];
self.tabBarController.viewControllers = [NSArray arrayWithObjects:viewController1, secondNavController, nil];
self.window.rootViewController = self.tabBarController;
[self.window makeKeyAndVisible];
}
else
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
SigninTabBarTemplateViewController *landingPage = [[SigninTabBarTemplateViewController alloc] initWithNibName:#"SigninTabBarTemplateViewController" bundle:nil];
self.window.rootViewController = (UIViewController *) landingPage;
[self.window makeKeyAndVisible];
}

There are many options you can consider.
This can be easily achieved with the use of delegate. If you want to close the VC that you presented modally, give it a delegate property. The delegate will be sent a message when required, letting it dismiss the VC. A good way to go with delegate is to write a custom procotol.
For example :
// the delegate will conform to this protocol
#protocol SignInVCDelegate
// this method will be called when required
//
-(void)signInCompleted;
#end
Now, make the object you want conforms to that protocol, for example the app delegate.
// .h
#import "SignInVCDelegate.h"
#interface YourAppDelegate : NSObject <..., SignInDelegate> {
...
SignInVC *signIn;
...
}
-(void)signInCompleted;
#end
The implementation looks like this :
// .m
...
-(void)signInCompleted {
...
[signIn.view removeFromSuperview];
}
-(BOOL)applicationDidFinishLaunching {
if(!logged) {
...
[signIn setDelegate:self];
[self.tabBarController presentModalViewController:signIn
animated:YES];
}
}
Now give signInVC a delegate property, which will be set before being presented modally, and send the delegate a message when the sign in process is completed.
// in .h
#property(retain) id <SignInDelegate>delegate;
// in .m
#synthesize delegate;
-(IBAction)validateSignIn {
...
[delegate signInCompleted];
}
You can write any method you want, this example is simplist, and it is useful to give the delegate some informations. In this case for example you could pass a user name, or user id, or what ever you want.
Another simple option is using notifications. This option lets any object informed when something happen, as long as it register for it. Given the same objects as the previous example, the app delegate will register for the notification, while the sign in view controller will post it.
// in app delegate .m
-(BOOL)applicationDidFinishLaunching {
...
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(signInCompleted)
name:#"UserSignedInNotification"
object:nil];
}
// in SignInVC.m
-(IBAction)validateSignIn {
...
[[NSNotificationCenter defaultCenter]
postNotificationName:#"UserSignedInNotification"
object:self];
}
More informations about delegates and notifications in Communicating with Objects.

You could try doing something like this in the method where you know the user has successfully logged in. (Assuming SignedInTabbarViewController is your TabBarController)
SignedInTabbarViewController *signedInTabbarViewController = [[SignedInTabbarViewController alloc] init];
id mainDelegate = [[UIApplication sharedApplication] delegate];
[self.navigationController.view removeFromSuperview];
if( [mainDelegate respondsToSelector:#selector(setViewController:)]) {
[mainDelegate setViewController:signedInTabbarViewController];
}
UIWindow *mainWindow = [mainDelegate window];
[mainWindow addSubview: signedInTabbarViewController.view];
[signedInTabbarViewController release];

Related

Add UINavigationItem to UINavigationController that wraps Google Drive OAuth

Goal: For connection to iOS Google Drive, wrap the iOS Google OAuth view controller in a programmatically created navigation controller, and add a Cancel button to enable the user to cancel the Google OAuth process, should they choose to do so.
Problem: While I can successfully wrap the OAuth view controller in a navigation controller, I cannot seem to add a navigation item, such as the desired Cancel button.
I add a navigation controller that wraps the Google Drive OAuth view controller, as follows...
GTMOAuth2ViewControllerTouch *authViewController = nil;
if (!self.isAuthorized) {
SEL selectorFinish = #selector(viewController:finishedWithAuth:error:);
SEL selectorButtonCancel = #selector(buttonCancelTapped:);
authViewController = [[GTMOAuth2ViewControllerTouch alloc] initWithScope:kGTLAuthScopeDrive
clientID:kClientID
clientSecret:kClientSecret
keychainItemName:kKeychainItemName
delegate:self
finishedSelector:selectorFinish];
UINavigationController *navController = [[UINavigationController alloc] init];
[navController addChildViewController:authViewController];
[self.parentTVC presentViewController:navController animated:YES completion:nil];
}
For clarity, the variable parentTVC is a public property,
#property (nonatomic, strong) UITableViewController *parentTVC;
and is set using a custom init method, as follows...
- (id)initWithParentTVC:(UITableViewController *)tvc {
self = [super init];
[self setParentTVC:tvc];
return self;
}
I have attempted to add UINavigationItems to the UINavigationController instance navController, however this does not work, and instead I seem to be stuck with the UIView with the two small buttons (< and >) in the nib file GTMOAuth2ViewTouch.xib, image included below...
I have read through the GTL file GTMOAuth2ViewControllerTouch.m to attempt to see whether there is a method I could possible use or whether I can override by subclassing, but I am not confident in my attempts to do this.
My best guess is that any navigation controller wrapping the OAuth view controller set by this code from GTMOAuth2ViewControllerTouch.m...
- (void)setUpNavigation {
rightBarButtonItem_.customView = navButtonsView_;
self.navigationItem.rightBarButtonItem = rightBarButtonItem_;
}
Assistance please?
This is my re-interpretation of Imran Khan's excellent answer provided in his response to this stack overflow question: Google Drive iOS SDK: Display Cancel Login Button
The googleAuthCheck method should be called in either the viewDidLoad or viewWillAppear method of the parent view controller. (I assume here a reasonable understanding of iOS Google Drive SDK, so let me know if I need to add further clarification.)
Also, albeit a small issue, using initWithBarButtonSystemItem:UIBarButtonSystemItemCancel requires that only the title text of the view controller then needs to be localised (if you are implementing localisation).
- (void)googleAuthCheck {
if (!self.isAuthorized) {
SEL selectorFinish = #selector(viewController:finishedWithAuth:error:);
SEL selectorButtonCancel = #selector(buttonCancelTapped:);
UINavigationController *navController = [[UINavigationController alloc] init];
UINavigationItem *navigationItem = [[UINavigationItem alloc] initWithTitle:<<localised string for title>>];
UIBarButtonItem *barButtonItemCancel = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:selectorButtonCancel];
UINavigationBar *navigationBar = [[UINavigationBar alloc] initWithFrame:CGRectMake(0, 0, 320, 63)];
[navigationItem setRightBarButtonItem:barButtonItemCancel];
[navigationBar setTranslucent:NO];
[navigationBar setItems:[NSArray arrayWithObjects: navigationItem,nil]];
[navController.view addSubview:navigationBar];
GTMOAuth2ViewControllerTouch *authViewController = nil;
authViewController = [[GTMOAuth2ViewControllerTouch alloc] initWithScope:kGTLAuthScopeDrive
clientID:kClientID
clientSecret:kClientSecret
keychainItemName:kKeychainItemName
delegate:self
finishedSelector:selectorFinish];
[navController addChildViewController:authViewController];
[self.parentTVC presentViewController:navController animated:YES completion:nil];
}
}
For clarity, the buttonCancelTapped: method is as follows...
- (IBAction)buttonCancelTapped:(UIBarButtonItem *)sender {
[self.parentTVC dismissViewControllerAnimated:YES completion:^(void){}];
}
For clarity, the variable parentTVC is a public property,
#property (nonatomic, strong) UITableViewController *parentTVC;
and is set using a custom init method, as follows...
- (id)initWithParentTVC:(UITableViewController *)tvc {
self = [super init];
[self setParentTVC:tvc];
return self;
}
This custom init method is called from the parent view controller.

close modalvew controller and parentview

I am trying to open a modalview from a view like that,
SignupViewController *signUpView = [[SignupViewController alloc] initWithNibName:#"SignupViewController" bundle:nil];
[signUpView setModalTransitionStyle:UIModalTransitionStyleCrossDissolve];
self.parentViewController.view.transform = CGAffineTransformMakeScale(1.3, 1.3);
self.parentViewController.view.alpha = 0;
[UIView animateWithDuration:.35 animations:^{self.parentViewController.view.alpha = 1.0; self.parentViewController.view.transform = CGAffineTransformMakeScale(1, 1);}];
[self presentModalViewController:signUpView animated:YES];
After login i am closing the modalview and redirecting to anther view, but the parentview is still there,
[self dismissViewControllerAnimated:YES completion:^{
ToolsViewController *gototoolpage = [[ToolsViewController alloc] initWithNibName:#"ToolsViewController" bundle:nil];
[self.navigationController pushViewController:gototoolpage animated:YES];
}
How to dismiss the parentview also. Any idea
You code looks a little confused. What do you intend by references to parentViewController? Check the docs - it is the containing viewController, not the previous or presenting viewController. In a NavigationController context this would be the UINavigationController. In a modal view context there is no parentViewController, but there is a presenting ViewController. I am not sure what you intend by all of those calls to self.parentViewController.
In any case you should really be sending the dismiss request back to your presenting viewController via a delegate so that it is completely clear where the pushViewController message is being passed from and to.
In the header file of your signUpViewController declare a protocol:
#protocol SignUpViewControllerDelegate
- (void) dissmissSignUpVC;
#end
then in your presentingViewController, after
SignupViewController *signUpView = [[SignupViewController alloc] initWithNibName:#"SignupViewController" bundle:nil];
add
[signUpView setDelegate:self];
and implement the delegate method with the same code you now have in your completion block:
- (void) dissmissSignUpVC {
ToolsViewController *gototoolpage = [[ToolsViewController alloc]
initWithNibName:#"ToolsViewController" bundle:nil];
[self.navigationController pushViewController:gototoolpage animated:YES];
}
In signUpView invoke the delegate's method to dismiss:
[[self delegate] dissmissSignUpVC];
Watch out for those stacked animations, I suspect that only the first will be performed (i.e. gototollpage animated:YES might as well be gototoolpage animated:NO)
Perhaps anyway you should reconsider your logic. I imagine the user might have a confusing experience if you do this under-the-hood manipulation of viewControllers. Better that there is a UI control for the user to navigate to toolsViewController so they understand where they are?

self.navigationController == nil after awaking from LocalNotification

I'm working in Xcode 4.3.2
I implemented Local Notifications that alert the user of a new event at a predetermined time. So when my app is in the background and the clock strikes 8 am (for instance), the user will get a notification from my app.
When the user decides to view the app from the background I load a nib. Currently, this nib works properly: it shows the view as it was it arranged in the nib. However, after the nib is shown to the user, I want to forward the user to a different view in the LocalNotificationsHandler.m. When I attempt to push the second view, my app fails. So while there isn't an error message, it seems the second nib will not load.
In short the flow goes as follows:
user gets notification while my app is running in the background
user chooses to view the app
the LocalNotificationsHandler nib will load
self.navigationController == nil (in LocalNotificationsHandler.m)
self.navigationController will not "[pushViewController: "new view" animated:YES]" to get a new view
I'm wondering if there is something I'm missing from my AppDelegate.m file so I've included
"didFinishLaunchingWithOptions" from my AppDelegate.m file:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
// Add the navigation controller's view to the window and display.
NSLog(#"did finish launching with options");
[self.window addSubview:tabBarController.view];
[self.window makeKeyAndVisible];
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound)];
if (self.locationManager == nil)
{
locationManager = [[CLLocationManager alloc] init];
locationManager.purpose = #"We will try to use you location";
}
if([CLLocationManager locationServicesEnabled])
{
[self.locationManager startUpdatingLocation];
}
self.navigationController.navigationBar.tintColor = nil;
return YES;
}
You are using the outdated (since iOS 3) method of adding the viewcontroller's view to the main UIWindow. That should be looking like this:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// create properly sized window
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// create instance of root VC and assign to window
MainViewController *vc = [[MainViewController alloc] init];
self.window.rootViewController = vc;
[vc release];
[self.window makeKeyAndVisible];
return YES;
}
The navigationController property of a view controller is ONLY set if it is actually presented from a UINavigationController.
See this writeup for more information: http://www.cocoanetics.com/2012/11/revisited/

How to get the user's choice properly when the choice is too complex to use UIAlertView

I have been struggling with this problem for a while now, so any help would be greatly appreciated.
Here is the situation: My application has a UIViewController subclass called InitialViewController. This view controller has a UIButton, and when that button is pressed it creates a NSObject subclass called MyEngine. Something like this:
#interface InitialViewController : UIViewController <MyEngineDelegate>
...
#end
#implementation InitialViewController
...
-(IBAction)pressedButton:(id)sender {
MyEngine *engine = [[MyEngine alloc] init];
[engine start];
}
Inside start, I present a ViewController (ConflictViewController) modally to get the user's choice:
#interface MyEngine : NSObject <ConflictViewControllerDelegate>
...
-(void) start;
#end
#implementation MyEngine
...
-(void) start {
ConflictViewcontroller *cvc = [[ConflictViewController alloc] initWithNibName:#"ConflictViewController" bundle:nil];
cvc.modalPresentationStyle = UIModalPresentationFormSheet;
cvc.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
cvc.delegate = self;
UIWindow *window = [(MyAppDelegate *) [[UIApplication sharedApplication] delegate] window];
[[window rootViewController] presentModalViewController:cvc animated:YES];
}
#end
ConflictViewController is really simple. It just waits for the user to decide, and when the user press the button, it send the message to the delegate, and dismiss itself.
-(IBAction)didSelectConflict:(id)sender {
UISegmentedControl *seg = (UISegmentedControl*) sender;
[self.delegate didResolveConflictChoice:seg.selectedSegmentIndex];
[self dismissModalViewControllerAnimated:YES];
}
I've checked every connection, all the delegates are working properly.
What is going wrong is:
When MyEngine receives the user's choice in it's implementation of didSelectConflict: it cannot continue properly because all of it's properties have gone null.
When the MyEngine presents the ConflictViewController, the program continues the execution and when start finishes, it goes back to pressedButton: and when this method is closed, the MyEngine object gets released.
What i want to know is if there is way around this ? Has anyone done something like this in another way ?
The question here is: How to get the user's choice properly when the choice is too complex to use UIAlertView.
Sorry for the long question, I simplified it as much as I could. Thanks for your time, any links, comments, or any kind of help is greatly appreciated
Why are you initializing MyEngine *engine in the IBAction, if you wish to use a MyEngine object why don't you make a global declaration in your InitialViewController and just call [engine start] in the IBaction. Then when the delegate method returns the selected index you can apply that to a global int in your initial view controller and continue on your way. Hope that makes sense
Make your method start as
-(void) startWithDelegate:(id)del {
ConflictViewcontroller *cvc = [[ConflictViewController alloc] initWithNibName:#"ConflictViewController" bundle:nil];
cvc.modalPresentationStyle = UIModalPresentationFormSheet;
cvc.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
cvc.delegate = del;
UIWindow *window = [(MyAppDelegate *) [[UIApplication sharedApplication] delegate] window];
[[window rootViewController] presentModalViewController:cvc animated:YES];
}
and
-(IBAction)pressedButton:(id)sender {
MyEngine *engine = [[MyEngine alloc] init];
[engine startWithDelegate:self];
}
implement didResolveConflictChoice: in InitialViewController and get the delegate call there.
OR you can use UIActionSheet if suitable.

Showing Viewcontrollers modally in a delege functions

I have some code that shows two UIViewController in a delegate.
RootViewController.m
request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:#"***some https url here ***"]];
// custom implementation of NSURLConnectionDelegate
dataman = [[DataManager alloc] initWithParentcontroller:self];
mainConn = [[NSURLConnection alloc] initWithRequest:request delegate:dataman];
In AuthenticationViewController.h
#protocol ShowAuthenticationWindowDelegate <NSObject>
#required
- (void) onFinishedEnteringCredentials:(NSURLCredential*)credentials;
- (void) onCancelAuthentication;
#end
in AuthenticationViewController.m
- (IBAction) onClickLogin:(id)sender;
{
....
// authDelegate => id <ShowAuthenticationWindowDelegate>
[authDelegate onFinishedEnteringCredentials:credentials];
[self dismissModalViewControllerAnimated:YES];
....
}
in DataManger.h (DataManager class) implements the NSURLConnectionDelegate and ShowAuthenticationWindowDelegate.
In Datamanager.m
In the didReceiveAuthenticationChallenge delegate function I show the AuthentiationViewController as a modal dialog to gather username/password.
-(void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
AuthenticationViewController *authview = [[AuthenticationViewController alloc] initWithNibName:#"AuthenticationViewController" bundle:[NSBundle mainBundle]];
authview.modalPresentationStyle = UIModalPresentationFullScreen;
authview.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
authview.credentialsDelegate = self;
[rootController presentModalViewController:authview animated:YES];
}
Here I show a UIViewController which is an activity indicator in a view. I am showing it modally after I dismiss the previous AuthenticationViewController dialog in one of the login button event handler by called dismissModalViewController. After sending the credentials with challenge object (previously cached) I am showing the ActivityViewController modally, but it is not shown no matter what I do. I tried to show an UIAlertView which works, but my activityviewcontroller is not shown. I checked the parameters and objects everything is valid. even the delegate wire ups!!! All the code is getting called but the dialog is not shown.
May be I am missing something ???
- (void) onFinishedEnteringCredentials:(NSURLCredential*)credentials;
{
[[authChallenge sender] useCredential:credentials forAuthenticationChallenge:authChallenge];
// create an activity modal dialog
if (activityController == nil) {
activityController = [[ActivityViewController alloc] initWithNibName:#"ActivityViewController" bundle:[NSBundle mainBundle]];
activityController.modalPresentationStyle = UIModalPresentationFullScreen;
activityController.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
}
[rootController presentModalViewController:activityController animated:YES];
}
I have figure out the solution, if anyone want to show two modal dialogs back to back, you should set the "animated" parameter to "NO" on the controller that is being dismissed. It seems like the animation transition is not being completed by the time the next controller is shown with "presentViewController" function.