I have the main view controller and when the viewDidLoad the user is validated on login via Parse and Facebook:
if ([PFUser currentUser] == nil && [FBSDKAccessToken currentAccessToken] == nil) {
WelcomeViewController *welcomeController = [self.storyboard instantiateViewControllerWithIdentifier:#"Welcome"];
[self presentViewController:welcomeController animated:YES completion:nil];
}
When I try to push data from the welcome view controller I use self.presentingViewController:
MainFeedTableViewController *mc = (MainFeedTableViewController *)self.presentingViewController;
mc.username = self.fbUsername;
BUT
the compiler sees it as UINavigationController:
-[UINavigationController setUsername:]: unrecognized selector sent to instance 0x7f92b9009a00.
How is that possible when I am presenting with self of the MainFeedTableViewController?
When you call the [self presentViewController:welcomeController animated:YES completion:nil]; The presentation is done via UINavigationController (I am assuming you have a navigation controller in your application). What it does is presents the view inside its Navigation hierarchy you can use the following to get the MainFeedTableViewController if your MainFeedTableViewController is the first view in Hierarchy
MainFeedTableViewController * viewController = (MainFeedTableViewController*)[self.navigationController.viewControllers objectAtIndex:0];
If its not the first one and you want to use relative positioning you can do something like this
MainFeedTableViewController * viewController = (MainFeedTableViewController*)[self.navigationController.viewControllers objectAtIndex:
[self.navigationController.viewControllers indexOfObject:self ]- 1];
Which will first find the Index of self (WelcomeViewController) and then find the view in UINavigationController views array a view which is just behind this one.
Related
When my app resumes I find the top most view controller (whatever was last presented when the app was active) in one of my app delegate methods. I know it works because calling viewDidLoad runs the one from the right view controller.
However I only want to call viewDidLoad if the top most controller is one of a particular few view controllers. How do I do this? I've attempted returning the restoration id, title and navigationitem.title of the view controller to find which one it is but all return (null). I set the restoration id programmatically before I call any view controller so it should be ok, but somehow is not when accessed in the app delegate.
Edit:
Process is as follows:
The first time the app starts the app delegate method didFinishLaunchingWithOptions checks if some data is stored. If it is, the following code is run:
AACMainViewController *firstController = [storyBoard instantiateViewControllerWithIdentifier:#"aacmainViewController"];
firstController.restorationIdentifier = #"AACMainViewController";
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:firstController];
self.window.rootViewController = navController;
The user is now in the main menu. If at this point the user shrinks the app and then resumes it, the app delegate method applicationDidBecomeActive is eventually called. When it is, it runs another method. The code we're interested in is this:
UIViewController * top = [self topViewController];
//[top viewDidAppear:YES];
NSString * toptitle = top.title;
NSString * resid = top.restorationIdentifier;
NSLog(#"App top res ID is %#",resid);
NSLog(#"App top title is %#",top title);
The method topViewController and the method it calls are:
- (UIViewController *)topViewController
{
return [self topViewController:[UIApplication sharedApplication].keyWindow.rootViewController];
}
- (UIViewController *)topViewController:(UIViewController *)rootViewController
{
NSLog(#"%#",#"topViewController: ---");
if (rootViewController.presentedViewController == nil)
{
NSLog(#"%#",#"presentedViewController == nil");
return rootViewController;
}
//isMemberOfClass: //isKindOfClass:
if ([rootViewController.presentedViewController isMemberOfClass:[UINavigationController class]])
{
NSLog(#"%#",#"isMemberOfClass");
UINavigationController *navigationController = (UINavigationController *)rootViewController.presentedViewController;
UIViewController *lastViewController = [[navigationController viewControllers] lastObject];
return [self topViewController:lastViewController];
}
NSLog(#"%#",#"End");
UIViewController *presentedViewController = (UIViewController *)rootViewController.presentedViewController;
return [self topViewController:presentedViewController];
}
As it stands, although a title is shown in the Main view when you are running the app, the NSLog in the app delegate will think it is null!
Edit 2:
I found that if you manually set the title in viewDidLoad or viewDidAppear BEFORE you shrink it, then it knows about the title on resume. Which doesn't make sense since the title is already shown on the device. It also presents a problem. Once you set the title in code this way (let's say title is "Test1"), it doesn't matter which view you shrink and resume from, unless you set the title in code for that new view controller (say "Test2"), it'll still think the title is the last one ("Test1"). I've a lot of view controllers so manually setting it is too tedious to contemplate.
I have two view controllers. First is empty, second contains a text field. If this field is empty, I need to move to the second controller automatically.
I tried this:
NSUInteger VCcount = self.navigationController.viewControllers.count;
UIViewController *btVC = self.navigationController.viewControllers[VCcount-2];
if([self.btViewController.Text.text isEqualToString:#""])
{
[self.navigationController pushViewController:btVC animated:YES];
}
and this:
UIViewController* btVC = [storyboard instantiateViewControllerWithIdentifier:#"BTViewController"];
But at the first launch program knows only current controller and doesn't know about thesecond.
How can I get there?
See the example below:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
self.textField.text = #"some text";
if (self.textField.text.length == 0) {
UIViewController *secondVC = [self.storyboard instantiateViewControllerWithIdentifier:#"secondVC"];
[self.navigationController pushViewController:secondVC animated:NO];
}
}
If you comment out the assignment of "some text" to the textField, the secondVC will be instantiated and presented. I don't know what exactly you are trying to achieve with first two lines of your presented code.
If the secondVC already exist, you can just take a reference to it by knowing its index on the stack, or inspecting viewControllers array of your navigationController before you push it to the stack. If it does not exist, you just instantiate it from storyboard (with the correct identifier) and push it.
After dismissing my second view controller, I would like to call a function in my first view controller.
The code I am using now is this
firstViewController *fvc = (firstViewController *)self.parentViewController;
[self dismissViewControllerAnimated:YES completion:^ {
[fvc someMethod];
}];
But it seems that the method is never called, I added a UIAlertView to the method so it would be called the moment the method is called too.
firstViewController *fvc = (firstViewController *)self.parentViewController;
[fvc someMethod];
[self dismissViewControllerAnimated:YES completion:nil];
This totally fails too, I don't get any error from both ways.
firstViewController *fvc = (firstViewController *)self.parentViewController;
[self dismissViewControllerAnimated:YES completion:^ {
[fvc someMethod];
}];
You should use presentingViewController insteadof parentViewController when self is being presented
like
firstViewController *fvc = (firstViewController *)self.presentingViewController;
[self dismissViewControllerAnimated:YES completion:^ {
[fvc someMethod];
}];
As rdurand said, try presentingViewController instead. Also, would be very helpful if you could put in a NSLog() in your code to show you what's in fvc at the point of method invocation. rdurand is probably right that your fvc pointer is nil, so you're invoking the method on nothing. If the method isn't activating, then there are really only a few possibilities:
Your line of code is not executing
It's executing, but you're invoking the method on a nil object
The method is executing, but on a different object than you're expecting
Set a breakpoint, look at your VC pointer, make sure the code is following the path you think it is. Pretty standard debugging task, unless I'm missing something...
As I commented, the UIViewController documentation states :
parentViewController
The parent view controller of the recipient.
(read-only)
#property(nonatomic, readonly) UIViewController *parentViewController
Discussion
If the recipient is a child of a container view controller,
this property holds the view controller it is contained in. If the
recipient has no parent, the value in this property is nil.
Prior to iOS 5.0, if a view did not have a parent view controller and
was being presented, the presenting view controller would be returned.
On iOS 5, this behavior no longer occurs. Instead, use the
presentingViewController property to access the presenting view
controller.
So basically, they did not really remove parentViewController, but the behavior you could expect before iOS 5 when your view controller doesn't have a parent view controller has changed.
I have a UITabBarController-based application in which my login page is presented modally by the default tab's VC(UITabBarController index 0), and is dismissed modally by dismissViewControllerAnimated:.
From my settings page I have a logout button, and if the user immediately logs back in I have to call [self.presentingViewController.presentingViewController dismissViewControllerAnimated:YES completion:nil]; and then reset the UITabBarController's selectedIndex property in order to get rid of the settings page and also go back to the starting tabBar tab. So I do this:
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSString* responseString = [[NSString alloc] initWithData:_responseData encoding:NSUTF8StringEncoding];
/*Succesful Login*/
if([responseString isEqualToString:#"success"])
{
UITabBarController *tabBarController = (UITabBarController *)self.presentingViewController;
if (tabBarController){
NSLog(#"I have a tab bar~");
[self.tabBarController setSelectedIndex:0];
}
else{
NSLog(#"I don't have a tab bar~");
}
//dismisses from a second, immediate, re-login attempt
[self.presentingViewController.presentingViewController dismissViewControllerAnimated:YES completion:nil];
//dismisses from first login attempt
[self dismissViewControllerAnimated:YES completion:nil];
NSLog(#"Succesful login.");
}
In this case, the method setSeletedIndex: does not work because this methodology essentially creates a new instance of the tabBarController, when it is the original that is actually displayed. I CAN NOT seem to access the already existing UITabBarController which was before my modally presented login page.
EDIT:
I have tried using this conditional to check its existence:
if (self.tabBarController){
NSLog(#"I have a tab bar~");
//[self.tabBarController setSelectedIndex:0];
}
else{
NSLog(#"I don't have a tab bar~");
}
where I have even changed self.tabBarController to self.presentingViewController.tabBarController and get nil, and self.presentingViewController.presentingViewController.tabBarController and get nil, how am I to access the previously existing UITabBarController?
Objective: Access the application's UITabBarController that is present before the presentation of the login page. (Access the Appdelegate's UITabBarController)
Your use case is a natural fit for delegation.
In the header file for your Login, define a delegate. E.g.,
#class Login
#protocol LoginDelegate <NSObject>
#required
- (void) userDoneWithLoginController:(Login *) controller;
#end
#interface Login: UIViewController
#property (weak, nonatomic) id<LoginDelegate> viewDelegate;
#end
Then your presenting view controller segue can set itself as the delegate to the login view controller by setting this "viewDelegate" object inside the prepareForSegue for the presenting view controller before control passes to the Login page. [Let me know if you want this clarified.]
In addition the presenting view controller will implement the required method--which can simply dismiss the login page when called
-(void) userDoneWithLoginController:(Login *) controller
{
[controller dismissViewControllerAnimated:YES completion:nil];
}
Once you've done this, the login page simply calls its delegate method when it is done, thusly:
[self.viewDelegate userDoneWithLoginController:self];
Viola!
This is preferred way to dismiss a modal view in iOS. The calling controller should be the controller dismissing the view. Try to avoid having view controllers dismiss themselves. Instead, have their calling/presenting controllers (delegates) dismiss them.
The following should work.
// check that the vc presenting your current modal vc is a tab bar controller
if ([self.presentingViewController isKindOfClass:[UITabBarController class]]) {
// get the pointer of type UITabBarController
UITabBarController *tabBarController = (UITabBarController *)self.presentingViewController;
// set to the desired tab
tabBarController.selectedIndex = 2;
}
// dismiss the current modal vc
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
I believe I found my solution.
I was able to access the app's UITabBarController from the Appdelegate like so:
MyAppDelegate *appDelegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
[[appDelegate myTabBar] setSelectedIndex:0];
I am working on an app that at launch checks for valid login credentials, and if they are found and not expired the main split view controller is displayed, and if not a login screen should be displayed.
Each part is working fine separately, but I am struggling with the best way at launch time to select the proper view to display.
I have tried setting up a modal segue from the root view controller, and in my application:didFinishLaunchingWithOptions: function in the App Delegate, calling this:
// Segue to the login view controller...
if (loginNeeded) {
[self.window.rootViewController performSegueWithIdentifier:#"LoginScreen" sender:self];
}
This logically should work, but triggering segues from within the app delegate seems to be impossible.
What is the ideal place and technique for handling this?
You could try a custom segue, as per this post hiding-a-segue-on-login-process.
Alternatively if you're desperate to have the login display before the split view controller loads try something along the following lines...
Create your login screen on the main storyboard as, say, a subclass of UIViewController. Make sure it is the initial scene (check Is Initial View Controller).
On the storyboard, create a new segue from your login class to the original SplitViewController. Give it an identifier, 'Load SplitViewController' and a segue custom class name which we'll call FullyReplaceSegue.
In your login class .m file, add code to be called once the user has logged in:
[self performSegueWithIdentifier:#"Load SplitViewController" sender:self];
Create the new segue class, based on UIStoryboardSegue and name it FullyReplaceSegue as per above.
.h file
#import <UIKit/UIKit.h>
#interface : UIStoryboardSegue
#end
.m file
#import "FullyReplaceSegue.h"
#implementation FullyReplaceSegue
- (void)perform
{
UIViewController *dest = (UIViewController *) super.destinationViewController;
UIWindow *window = [UIApplication sharedApplication].keyWindow;
window.rootViewController = dest;
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
UISplitViewController *splitViewController = (UISplitViewController *)dest; // assumes we're transitioning to a UISplitViewController!
UINavigationController *navigationController = [splitViewController.viewControllers lastObject];
splitViewController.delegate = (id)navigationController.topViewController;
}
}
#end
Here's how I did it.
In didFinishLaunchingWithOptions:
//save the root view controller
[[self window] makeKeyAndVisible];
UINavigationController *navigationController = (UINavigationController*) self.window.rootViewController;
rootController = [[navigationController viewControllers] objectAtIndex:0];
Somewhere else in the app delegate:
[rootController performSegueWithIdentifier:#"fileSegueID" sender:self];
Then, in the storyboard, create a segue from the view that gets assigned as "rootController", to the desired optional view, and give that new segue the id fileSegueID. It takes some debugging to make sure the rootController variable gets assigned to the correct view.
Maybe a little late, but I was looking for the same suggestions. Here's what I wound up doing.
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Signup" bundle:nil];
if(isLoggedIn) {
UINavigationController *navigationController = (UINavigationController*) self.window.rootViewController;
IndexController *ivc = [storyboard instantiateViewControllerWithIdentifier:#"IndexController"];
[navigationController pushViewController:ivc animated:NO];
}
Why don't you load the screen that would be visible assuming proper and non-expired log-in credentials (by setting it as the root view controller of the window), and then in viewDidLoad of that first view controller, check if an update to the login credentials are needed. If so, segue into the login view controller.
Yes, it can be used, if you get a reference to the segue's parent view controller. You can get it like this:
UINavigationController *navigationController = (UINavigationController*) self.window.rootViewController;
[[[navigationController viewControllers] objectAtIndex:0] performSegueWithIdentifier:#"LoginScreen" sender:self];
This will only work if the index in viewControllers array matches the one of your view controller and if it exists of course. In this case is the first one (in the array and storyboard).
The segue ("LoginScreen") must not be attached to an action. The way you do this is by control-dragging from the file owner icon at the bottom of the storyboard scene to the destination scene. A popup will appear that will ask for an option in “Manual Segue”; pick “Push” as the type. Tap on the little square and make sure you’re in the Attributes Inspector. Give it an identifier which you will use to refer to it in code.