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.
Related
I have created several ViewControllers in a storyboard that each have their own class files. In AppDelegate I have programatically generated a UINavigationController that exists at the top of the app for every page. This will have two buttons that will be the same for every ViewController, one will load a ViewController called 'settings' and one will fire a method that reveals a side menu.
Screen shots to illustrate:
Currently, each ViewController has a button in the top left, that when pressed moves the current ViewController across revealing the menu below.
This works fine but what I want is for this button to be removed and replaced with the button that is on the NavigationController (currently place holder menu button seen in the purple NavigationController).
How do I implement the code that moves the ViewController in the AppDelegate, what the UINavigationController is generated?
AppDelegate.m
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:#"Main" bundle: nil];
MainViewController* mainVC = [mainStoryboard instantiateInitialViewController];
UINavigationController *navVC = [[UINavigationController alloc] initWithRootViewController:mainVC];
[mainVC setTitle:#"Congress app"];
UIBarButtonItem *showSettingsButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAction target:self action:#selector(showSettings:)];
UIBarButtonItem *showMenuButton = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:#"menuButton.png"] style:UIBarButtonItemStylePlain target:self action:#selector(revealMenu:)];
mainVC.navigationItem.leftBarButtonItem = showMenuButton;
mainVC.navigationItem.rightBarButtonItem = showSettingsButton;
[self.window setRootViewController:navVC];
[_window makeKeyAndVisible];
return YES;
}
- (IBAction)revealMenu:(id)sender
{
// This obviously won't work but what should go here instead?
// Something like get instance of MainViewController and fire it's reveal menu
// method but passing the current ViewController Id and running slidingViewController anchorTopViewTo:ECRight on that?
[self.slidingViewController anchorTopViewTo:ECRight];
}
MainViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
self.view.layer.shadowOpacity = 0.75f;
self.view.layer.shadowRadius = 10.0f;
self.view.layer.shadowColor = [UIColor blackColor].CGColor;
if (![self.slidingViewController.underLeftViewController isKindOfClass:[MenuViewController class]]) {
self.slidingViewController.underLeftViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"MenuVC"];
}
[self.view addGestureRecognizer:self.slidingViewController.panGesture];
self.menuBtn = [UIButton buttonWithType:UIButtonTypeCustom];
_menuBtn.frame = CGRectMake(8, 80, 34, 24);
[_menuBtn setBackgroundImage:[UIImage imageNamed:#"menuButton.png"] forState:UIControlStateNormal];
[_menuBtn addTarget:self action:#selector(revealMenu:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:self.menuBtn];
NSLog(#"MainVC loaded");
}
- (IBAction)revealMenu:(id)sender
{
[self.slidingViewController anchorTopViewTo:ECRight];
}
You're better off not doing that...
This isn't app delegate responsibility
There are 3rd party implementations on github / CocoaControls which offer this and manage the navigation bar
It is much better to rework your current view hierarchy than to force a connection from the app delegate.
The responsibility of the app delegate is to respond to app level events (like foreground / background notifications). It might be involved in setting up the initial UI but other than that is should do basically nothing.
I created an onscreen tutorial for my iOS app.
To accomplish this I'm using a UIPageViewController which is managing 3 viewControllers.
- (void)setupContentViews{
UIViewController *screenTutorial1 = [[UIViewController alloc] initWithNibName:#"ScreenTutorial_1ViewController" bundle:nil];
UIViewController *screenTutorial2 = [[UIViewController alloc] initWithNibName:#"ScreenTutorial_2ViewController" bundle:nil];
tutorialPages = #[screenTutorial1, screenTutorial2];
}
Everything works great, except that when I got to change the background for screenTutorial1 or screenTutorial2 it never gets called. What's the reason for this? Is there a solution?
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor redColor];
NSLog(#"line 30)");
}
After some experimentation it appears that if I add the code in UIPageViewController (see below) it sets the property. But what if I need to add any custom methods to my View Controllers? Do I need to do everything from UIPageViewController?
The problem is that at that point the view is nil so you can't change the backgroundColor.
You should subclass UIViewController and set the backgroundColor in viewDidLoad. And after that you should initialize the view controllers for the tutorialPages ivar like this:
YourUIViewControllerSubclass *screenTutorial1 = [[YourUIViewControllerSubclass alloc] initWithNimbName:#"ScreenTutorial_1ViewController" bunble:nil];
Update
Update your method setupContentViews like this:
- (void)setupContentViews
{
ScreenTutorial_1ViewController *screenTutorial1 = [[ScreenTutorial_1ViewController alloc] initWithNibName:#"ScreenTutorial_1ViewController" bundle:nil];
UIViewController *screenTutorial2 = [[UIViewController alloc] initWithNibName:#"ScreenTutorial_2ViewController" bundle:nil];
tutorialPages = #[screenTutorial1, screenTutorial2];
}
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];
I am wondering if there is a way to add an additional UITabBarItem to my exisiting UITabBarController. It doesn't need to be in runtime.
All I want to do is when hitting this button I want to presentModalViewController: over my actually visible ViewController, which should either be the TabBarController or its controllers.
Hopefully this is clear enough, if not, feel free to ask.
As a result of my research you cannot add a UITabBarItem to a UITabBar that is managed by a UITabBarController.
Since you maybe have added your UITabBarItem by adding a list of view controller, this is also the way of your choice to add further custom UITabBarItems, as i will show now:
Pre-Conditions:
As i mentioned before, you maybe have added your UITabBarItems by adding a list of view controller:
tabbarController = [[UITabBarController alloc] init]; // tabbarController has to be defined in your header file
FirstViewController *vc1 = [[FirstViewController alloc] initWithNibName:#"FirstViewController" bundle:[NSBundle mainBundle]];
vc1.tabBarItem.title = #"First View Controller"; // Let the controller manage the UITabBarItem
SecondViewController *vc2 = [[SecondViewController alloc] initWithNibName:#"SecondViewController" bundle:[NSBundle mainBundle]];
vc2.tabBarItem.title = #"Second View Controller";
[tabbarController setViewControllers:[NSArray arrayWithObjects: vc1, vc2, nil]];
tabbarController.delegate = self; // do not forget to delegate events to our appdelegate
Adding custom UITabBarItems:
Now since you know how to add UITabBarItems through adding view controller, you can also use the same way to add custom UITabBarItems:
UIViewController *tmpController = [[UIViewController alloc] init];
tmpController.tabBarItem.title = #"Custom TabBar Item";
// You could also add your custom image:
// tmpController.tabBarItem.image = [UIImage alloc];
// Define a custom tag (integers or enums only), so you can identify when it gets tapped:
tmpController.tabBarItem.tag = 1;
Modify the line above:
[tabbarController setViewControllers:[NSArray arrayWithObjects: vc1, vc2, tmpController, nil]];
[tmpController release]; // do not forget to release the tmpController after adding to the list
All fine, now you have your custom button in your TabBar.
What about handling events of this custom UITabBarItem?
Its easy, look:
Add the UITabBarControllerDelegate to your AppDelegate class (or the class which is holding the tabbarController).
#interface YourAppDelegate : NSObject <UIApplicationDelegate, UITabBarControllerDelegate> { }
Fit the protocol definition by adding this function:
- (void)tabBarController:(UITabBarController *)theTabBarController didSelectViewController:(UIViewController *)viewController {
NSUInteger indexOfTab = [theTabBarController.viewControllers indexOfObject:viewController];
UITabBarItem *item = [theTabBarController.tabBar.items objectAtIndex:indexOfTab];
NSLog(#"Tab index = %u (%u), itemtag: %d", indexOfTab, item.tag);
switch (item.tag) {
case 1:
// Do your stuff
break;
default:
break;
}
}
Now you have all you need to create and handle custom UITabBarItems.
Hope this helps.
Have fun....
Access the tabBar - property of your UITabBarController (reference), grab the elements array with the items - property (reference), add a new UITabBarItem to this array and use the tabBar's setItems:animated: - method to update your tab bar. Add an action to this tab bar to display the modal view controller.
In my AppDelegate, I create a UITabBarController and UINavigationController dynamically. I then add 3 views to it.
// Prepare the tab bar controller
tabBarController = [[UITabBarController alloc] init];
// Switch controller
UserSettingsController *settingsController = [[UserSettingsController alloc] init];
// Switches controller
SwitchesController *switchesController = [[SwitchesController alloc] init];
// Help controller
HelpController *helpController = [[HelpController alloc] init];
NSArray *controllers = [NSArray arrayWithObjects: switchesController, settingsController, helpController, nil];
tabBarController.viewControllers = controllers;
if (self.navigationController == nil) {
self.navigationController = [[UINavigationController alloc] initWithRootViewController:tabBarController];
}
[window addSubview:navigationController.view];
When I initially did this, I noticed that I now have a header/title bar at the top of my page. It's blank and really just takes up space. I'd like to utilize it though and add a "refresh" button to one of my views.
In the view I'm interested in, I attempted the following:
-(void)viewDidLoad {
[super viewDidLoad];
UIBarButtonItem *refresh = [[UIBarButtonItem alloc] initWithTitle:#"Refresh" style:UIBarButtonItemStylePlain target:self action:#selector(refreshSwitches:)];
self.navigationItem.rightBarButtonItem = refresh;
[refresh release];
}
-(void)refresh{
...
}
No button showed up, so I'm either way off or I'm missing something.
Any suggestions?
Thanks
I believe the problem is that your view controllers that you've added to the tabbar controller are not encapsulated by navigation controllers. Although your UITabBarController has a navigation controller, this does not implicitly give all of its tabs navigation controllers (and without a nav controller, a view controller's navigationItem won't do anything.) To remedy this problem I'd suggest encapsulating your view controllers with navigation controller upon initialization:
ex:
UserSettingsController *settingsController = [[UserSettingsController alloc] init];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:settingsController];
[settingsController release];
// etc...
NSArray *controllers = [NSArray arrayWithObjects:navController, ..., ..., nil];
tabBarController.viewControllers = controllers;
// Release your nav controllers, other cleanup
EDIT
Okay, I think I have a better idea of your setup now. I was able to get the following to work:
self.tabBarController.navigationItem.rightBarButtonItem = myButton;
An important caveat of this is that the button will persist on all of your tabbarcontroller's views unless you explicitly remove it. This may prove annoying/inelegant to maintain, but I am not sure of another solution if the view you want to have the button is not directly associated to a nav controller itself.