I'm experimenting with OAuth 2.0. which brings up a window containing a UIWebView as a canvas for the authentication server to communicate through. Right now, it is being shown as a modal view with its own view controller, and doesn't have a back button or cancel button. So the user has no way to escape from the sign-in process.
I want to have the webView handled by a navigation controller so I can push the webView's view controller.
I am having problems with doing this. It seems to me I should be able to just create a UINavigationController object with the root view controller being the main view controller, like this
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:self];
However, this line crashes the program without any error message in the debugger.
I thought I could place a line to push the web view controller immediately after the line above, like this:
[navController pushViewController:windowController animated:YES];
But, without getting past the first line, I can't begin to work out the details on getting the web view to show and then configure the back button.
UPDATE
Here is some context. This is in a single view application. The following code is in the main view controller, which brings up the sign-in dialog. I would like to replace the last line, where the presentModalViewController is called, with a push of the windowController onto a navController stack. Note the commented code at the end, where the initialization of the navigation controller is located.
- (IBAction)signInClicked:(id)sender {
if (![self isSignedIn]) {
// Sign in
[self runSigninThenInvokeSelector:#selector(updateUI)];
}
[self updateUI];
}
- (void)runSigninThenInvokeSelector:(SEL)signInDoneSel {
NSString *clientID = mClientId;
NSString *clientSecret = mClientSecret;
// Show the OAuth 2 sign-in controller
NSString *scope = [GDataServiceGoogleBlogger authorizationScope];
GTMOAuth2ViewControllerTouch *windowController;
windowController = [[GTMOAuth2ViewControllerTouch controllerWithScope:(NSString *)scope
clientID:(NSString *)clientID
clientSecret:(NSString *)clientSecret
keychainItemName:(NSString *)kKeychainItemName
delegate:(id)self
finishedSelector:#selector(windowController:finishedWithAuth:error:)] retain];
//UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:self]; // This causes a crash when it is not commented out.
[self presentModalViewController:windowController animated:YES];
}
The application delegate didFinishLaunchingWithOptions is set up this way:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
// Override point for customization after application launch.
self.viewController = [[[blogSpotViewController alloc] initWithNibName:#"myNibName" bundle:nil] autorelease];
self.window.rootViewController = self.viewController;
[self.window makeKeyAndVisible];
return YES;
}
Well, you can present this as a modal controller. You just need to arrange to call dismissModalViewController: in response to a button or some other event on the 'windowController'.
But if you want to use a navigation controller, then you can set that up in application:didFinishLaunchingWithOptions: like so:
// create window here like now
blogSpotViewController *viewController = [[blogSpotViewController alloc] initWithNibName:#"myNibName" bundle:nil];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:viewController];
[viewController release];
// TODO: hold onto this navController in a property if you really need that.
self.window.rootViewController = navController;
[navController release];
// present window here as you are now
This embeds your main view controller in a navigation controller. Then back in your runSigninThenInvokeSelector: you can...
[self.navigationController pushViewController:windowController animated:YES];
Hope that points you in the right direction.
Related
I'm having troubles hiding the UITabBarController which I defined as my rootViewController for the entire app.
I'm trying to hide the UITabBarController - which is the root view controller for the entire app - on the first view displayed. The idea is that the first view has UIImageView instances which jump to the defined UIViewControllers (which are also defined as view controllers of the root UITabBarController).
Is there a way to have the first view controller without the root UITabBarController but keep it for all other views defined as viewControllers?
Here's the code in AppDelegate defining the view controllers and the UITabBarController as rootViewController.
- (void)initViewControllers {
anIdeaVC = [[IdeaViewController alloc] initWithNibName:#"IdeaViewController" bundle:nil];
[anIdeaVC setTabBarItem:[[[UITabBarItem alloc] initWithTitle:#"Idea" image:[UIImage imageNamed:#"iconIdee.png"] tag:0] autorelease]];
aListTableVC = [[ListTableViewController alloc] initWithStyle:UITableViewStylePlain];
[aListTableVC setTitle:#"List"];
aListNC = [[ListNavigationController alloc] initWithRootViewController:aListTableVC];
[aListNC setTabBarItem:[[[UITabBarItem alloc] initWithTitle:#"List" image:[UIImage imageNamed:#"iconList.png"] tag:0] autorelease]];
anInnMapVC = [[MapViewController alloc] initWithNibName:#"MapViewController" bundle:nil];
anInnMapNC = [[InnMapNavigationController alloc] initWithRootViewController:anInnMapVC];
[anInnMapNC setTabBarItem:[[[UITabBarItem alloc] initWithTitle:#"InnMap" image:[UIImage imageNamed:#"iconInnMap.png"] tag:0] autorelease]];
aSearchTableVC = [[SearchTableViewController alloc] initWithNibName:#"SearchTableViewController" bundle:nil];
[aSearchTableVC setTitle:#"Search"];
aSearchNC = [[SearchNavigationController alloc] initWithRootViewController:aSearchTableVC];
[aSearchNC setTabBarItem:[[[UITabBarItem alloc] initWithTitle:#"Search" image:[UIImage imageNamed:#"iconSearch.png"] tag:0] autorelease]];
tabBarController = [[UITabBarController alloc] init];
[tabBarController setViewControllers:[NSArray arrayWithObjects:anIdeaVC, aListNC, anInnMapNC, aSearchNC, nil] animated:NO];
[tabBarController setSelectedViewController:anIdeaVC];
[tabBarController setDelegate:self];
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
[self initViewControllers];
[window setRootViewController:tabBarController];
[window makeKeyAndVisible];
return YES;
}
Thanks in advance for your help :-).
I think the best way to go about this is to make the vc with the icons the root to begin with. Then, when user makes a selection, create the tab bar vc and make it the root.
Create a view controller (not just a view) to show the icons and get the user selection. Make that the window's root on launch...
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
// don't do this
//[self initViewControllers];
// or this
//[window setRootViewController:tabBarController];
// instead do this, create the vc that lets user select an icon
// put your icon view in there
IconSelectVC *iconSelectVC = [[IconSelectVC alloc] init];
[window setRootViewController:iconSelectVC];
[window makeKeyAndVisible];
return YES;
}
Add the initViewControllers method to your app delegate's public interface, so it can be called from IconSelectVC. Then add one last line to it to make it replace the window's root vc.
// ... the rest of initViewControllers, then
[tabBarController setSelectedViewController:anIdeaVC];
[tabBarController setDelegate:self];
[window setRootViewController:tabBarController];
}
Now, in your IconSelectVC when you decide it's time to change the UI, get the app delegate singleton and change the window's root.
// in IconSelectVC.m
// when you decide to change to the tab bar.
// Be aware that this vc will be released here, so do any cleaning you need to do here
// e.g. unsubscribe from NSNotifications, clean any timers, finish any asynch requests, etc.
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate initViewControllers];
EDIT - We didn't discuss how this transition should look - my suggestion here will cause an "ugly" transition (in the eye of the beholder, of course) where the UI just changes in one frame. One way (among a few) to get a nicer transition would be to use os7 custom vc transitions.
I am trying to incorporate State Restoration in my app. I have it working fine for the most part, but presenting a navigation controller for a modal view on top of another navigation controller seems challenging.
For testing, I created a new split-view app on the iPad, with navigation controllers for both sides of the split view, and a Master and Detail view controller for each side, the roots of their respective navcontrollers. In the master view, you can click on a button to push a new TestViewController onto the navController stack programatically. I hook up the splitView in the storyboard, add restorationIDs to everything, opt-in to the delegate, provide a restoration class and adhere to the UIViewControllerRestoration protocol for TestViewController (since it's created programmatically) and everything works fine. If I close the app and retort it, it will start the TestViewController pushed onto the master's navcontroller. So far so good.
I then change the button handler to present the TestViewController inside a new UINavigationController, present it onto the master's navigation controller, to show a modal view (instead of pushing it on the nav stack). Now, when I relaunch the app, there is no modal view there anymore. TestModalViewController's viewControllerWithRestorationIdentifierPath:coder: is actually called correctly as before, but the modal view is never presented for some reason.
Here is the code for what I'm talking about
MasterViewController.h:
- (void)pushButton:(id)sender
{
TestModalViewController *test = [[TestModalViewController alloc] initWithNibName:#"TestViewController" bundle:nil];
test.restorationIdentifier = #"testid";
test.restorationClass = [TestModalViewController class];
UINavigationController *modal = [[UINavigationController alloc] initWithRootViewController:test];
modal.modalPresentationStyle = UIModalPresentationFormSheet;
modal.restorationIdentifier = #"ModalTestID";
[self.navigationController presentViewController:modal animated:YES completion:nil];
return;
}
TestModalViewController.m:
+ (UIViewController *) viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder {
TestModalViewController *test = [[TestModalViewController alloc] initWithNibName:#"TestViewController" bundle:nil];
test.restorationClass = [TestModalViewController class];
test.restorationIdentifier = [identifierComponents lastObject];
return test;
}
Perhaps the UINavigationController that is created to display modally is never preserved? Not sure why, because it does have a restorationIdentifier.
Edit:
After further testing, it turns out if I remove the UINavigationController from the the pushButton: code, and present the TestModalViewController instance directly, it gets restored correctly. So something about the UINavigationController being presented from another UINavigationController?
This works (though not what I really want):
- (void)pushButton:(id)sender
{
TestModalViewController *test = [[TestModalViewController alloc] initWithNibName:#"TestViewController" bundle:nil];
test.restorationIdentifier = #"testid";
test.restorationClass = [TestModalViewController class];
//UINavigationController *modal = [[UINavigationController alloc] initWithRootViewController:test];
//modal.modalPresentationStyle = UIModalPresentationFormSheet;
//modal.restorationIdentifier = #"ModalTestID";
[self.navigationController presentViewController:test animated:YES completion:nil];
return;
}
EDIT:
Attached link to test project: dropbox.com/sh/w8herpy2djjl1kw/vw_ZWqimgt
It's basically the Core Data master-detail template; run it on the iPad simulator. The + button in Master invokes the TestModalVC; if you then press the Home button, then kill debugger and launch again, you see the snapshot contains the TestModalVC but when the app is launched, it doesn't get restored
You can either create your own restoration class to handle this, or add the following to your app delegate:
- (UIViewController *)application:(UIApplication *)application viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents
coder:(NSCoder *)coder
{
NSString *lastIdentifier = [identifierComponents lastObject];
if ([lastIdentifier isEqualToString:#"ModalTestID"])
{
UINavigationController *nc = [[UINavigationController alloc] init];
nc.restorationIdentifier = #"ModalTestID";
return nc;
}
else if(...) //Other navigation controllers
{
}
return nil;
}
More information in the documentation.
I'm totally new to iOS programming. I only programmed on Android so far and Objective-C is a total different and new language for me now.
What I want to do is to not use a design that I've created with the storyboard. I want to do all programmatically, since I think it will be more dynamic if I do it like this.
The problem I'm encountering is, that I want to have 3 different views. I googled a bit, and stumbled upon some stackoverflow questions. There, people suggested using a NavigationController. Okay. Now I'm trying to implement it. What I want to have is the following
A MainViewController that has 3 different views. The first view is a loginView. The second one is displaying data and the third is displaying detailed data dependent on the click of the second view.
Is a navigationcontroller corerct for this? The problem I'm having is where I tell the app that I want to start with the MainViewController and push the LoginView in it.
I have a MainViewController.h and MainViewController.m that are subclasses of UIViewController
Now, where exactly do I do this? I have the didFinishLaunchingWithOptions method right here with the following content
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor];
UIViewController *viewController = [[MainViewController alloc]init];
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:viewController];
[navigationController pushViewController:viewController animated:NO];
self.window.rootViewController = navigationController;
[self.window makeKeyAndVisible];
return YES;
}
But that just crashes the app. What am I doing wrong? How do I get those three views? Am I starting completely wrong? Please help. As I said I'm new to iOS development. It's easy for me to programm on one view. I did that already, but I want thre different views! Thanks!
A MainViewController that has 3 different views. The first view is a loginView. The second one is displaying data and the third is displaying detailed data dependent on the click of the second view.
That's wrong.
You need three different view controllers, each of those will manage its own view.
Then you push one after another in the navigation controller, depending on user interaction.
Yes, Gonzalo Aune is rite, You should not push the rootviewcontroller in NavicationController.
Also , I will Suggest you to keep your first view (Login View) out of Navigation controller.
You can start with your MainViewController and based on check and conditions you can present LoginView on MainViewController using
[self presentViewController:loginViewController animated:YES completion:NULL];
And after successful login you can dismiss LoginViewController.
Remove this:
[navigationController pushViewController:viewController animated:NO];
You shouldnt push the ViewController since you told the NavigationController already that the ViewController would be the root one:
UINavigationController *navigationController = [[UINavigationController alloc]
initWithRootViewController:viewController];
use this code and if application is universion then use same code else remove the condition of ([[UIDevice currentDevice] userInterfaceIdiom]
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
UINavigationController *navController;
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
self.viewController = [[ViewController alloc] initWithNibName:#"ViewController_iPhone" bundle:nil];
{
if(result.length>0)
{
for(var i in result)
{
var ObjResult=result[i];
var content = "<div data-role='collapsible' id='set" + i + "'>";
content+="<h3>"+ObjResult.title+"<br>";
var intDate=parseInt(ObjResult['ordered date']);
content +=timestampToDate(intDate)+"</h3>"
if(isNaN(ObjResult.med_placeorderfor))
content+="<p><a>Medicle Place order for: </a>"+result[i].med_placeorderfor+"</p>";
if(isNaN(ObjResult.pres_placeorderfor)>0)
content+="<p><a>Medicle Place order for: </a>"+result[i].placeorderfor+"</p>";
if(ObjResult['order status'].length>0)
content+="<p><a>Order status: </a>"+ObjResult['order status']+"</p>";
if(ObjResult.comments.length>0)
content+="<p><a>Comments: </a>"+ObjResult.comments+"</p>";
content+="</div>";
}
$("#id_notification_list_dashboard").append( content ).collapsibleset('refresh');
$("#id_notification_list_dashboard").trigger('create');
}
else
{
$("#id_notification_list_dashboard").append("<div style=\"text-align:center\" data-role='list-divider'><h1>No data found</h1></div>").collapsibleset('refresh');
}
$('body').removeClass('ui-loading');
loadingWithMsg("hide");
}
} else {
self.viewController = [[ViewController alloc] initWithNibName:#"ViewController_iPad" bundle:nil];
}
navController=[[UINavigationController alloc]initWithRootViewController:self.viewController];
[navController.navigationBar setTranslucent:YES];
navController.navigationBar.tintColor = [UIColor colorWithRed:161.0f/255.0f green:18.0f/255.0f blue:6.0f/255.0f alpha:1];
self.window.rootViewController =navController ;
[self.window makeKeyAndVisible];
return YES;
}
I have been playing around with this for a couple of days and I cannot figure this out.
-> Basically I want to implement a simple login view that has a button when clicked, goes to go to the navigation controller ( in my case is "viewController" with buttons that link to mini math games which are other views).
-> Login screen should be displayed first, than navigation controller's root view when a button is clicked on the login screen
-> I have tried to declare the navigation controller when I click the button of the login screen but that seems to not work
-> Is it safe to say that a navigation controller can only be initialized in the apple delegate?
Currently I have this in my apple delegate declaring and setting my navigational controller:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions: (NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
self.viewController = [[ViewController alloc] initWithNibName:#"ViewController" bundle:nil];
UINavigationController *navigationViewController = [[UINavigationController alloc] initWithRootViewController:self.viewController]; // self.viewController is the main screen
self.window.rootViewController = navigationViewController; // set root to navigationViewController
[self.window makeKeyAndVisible];
return YES;
}
Any ideas will be appreciated. Thank you for your time !
Your code in the app delegate looks ok. NavigationController does not need to be declared in the AppDelegate. In your case, it is definitely ok to declare it upon login button pressed.
Try this at the login event:
UIViewController *nextVC = [[UIViewController alloc] init];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:nextVC];
[self presentViewController:navController animated:YES completion:^{
}];
What I would do would be set the loginViewController as the rootViewController of the navigation. And after check if login was done successfully, you could implement [self performSegueWithIdentifier:#"identifier"] setting the game viewController as destination. (Using Storyboard would make your life much easier). Well, that's my opinion :)
I have a tab bar application in Xcode 4.3 and I'm trying to insert a login screen before the tabbar is shown. The app works OK if presentModalViewController has animated:YESbut if it is without animation the view is not showing.
#synthesize window = _window;
#synthesize tabBarController = _tabBarController;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
UIViewController *viewController1 = [[FirstViewController alloc] initWithNibName:#"FirstViewController" bundle:nil];
UIViewController *viewController2 = [[SecondViewController alloc] initWithNibName:#"SecondViewController" bundle:nil];
self.tabBarController = [[UITabBarController alloc] init];
self.tabBarController.viewControllers = [NSArray arrayWithObjects:viewController1, viewController2, nil];
self.window.rootViewController = self.tabBarController;
LogInViewController *logViewController = [[LogInViewController alloc] initWithNibName:#"LogInViewController" bundle:nil];
[self.window addSubview:_tabBarController.view];
[self.tabBarController presentModalViewController:logViewController animated:YES];
//This wont work
//[self.tabBarController presentModalViewController:logViewController animated:NO];
[self.window makeKeyAndVisible];
return YES;
}
-(void)loginDone{
NSLog(#"back to the app delegate");
[self.tabBarController dismissModalViewControllerAnimated:YES];
}
Is this the right way to do it?
Why wont the code work with animated:NO ?
I also get this on output Unbalanced calls to begin/end appearance transitions for <UITabBarController: 0x689d350>.
First of all, move [self.window makeKeyAndVisible]; before your view controller setup.
Additionally, you should be presenting the modal view controller within the viewWillAppear: method of the view controller that will be visible first, to make sure your apps view hierarchy has been fully initialized before presenting your login screen.
Don't do this:
[self.window addSubview:_tabBarController.view];
Do this:
self.window.rootViewController = _tabBarController;
This will put the tabBarController on the screen. But that's not exactly what you want... My advise is:
1) Start by putting the logViewController has the rootViewController as I showed you above.
2) Once you got what you want (login is successful) just tell the AppDelegate to switch the rootViewController. This can be done in with delegation or notifications.
Also, as Toastor indirectly pointed out, you should start the presentViewController from the UIViewController who actually initiates it (and not from the AppDelegate).