i'm working with core data and navigation controllers. my story board consists of the following: (-> means connected)
UINavigationController -> UIViewController -> UITableViewController (master view controller) -> UITableViewController (detail view Controller)
Apple's Master-Detail Template offers the following in the appDelegate method application:didFinishLoadingWithOptions which actually work only if the storyboard contain
UINavigationController -> UITableViewController
UINavigationController *navigationController = (UINavigationController *) self.window.rootViewController;
SSViewController *controller = (SSViewController *)navigationController.topViewController;
controller.managedObjectContext = self.managedObjectContext;
this doesn't work in my case because navigation.topViewController will return the UIView Controller. So how can i reach the third view UITableViewController from the application:didFinishLoadingMethod ?
If by 'connected' you mean segues on the storyboard (which is the only logical way I can see to interpret your question), then the answer is that you can't directly.
Instead, you set the property on the UIViewController subclass (whatever the default master and/or detail VC is, as appropriate), and pass it on to UIViewController subclasses further down the chain in the prepareForSegue method.
It's often useful to implement this via a protocol, which appropriate VCs adopt:
#protocol ContextHolder <NSObject>
#property (strong, nonatomic) NSManagedObjectContext *managedObjectContext;
#end
In prepareForSegue, you can then do:
((id<ContextHolder>)segue.destinationViewController).managedObjectContext = self.managedObjectContext;
Related
I have ViewController 1 with navigation controller where I'd like to push some other VC. But I have ViewController 2 presented over it not in navigation controller hierarchy.
In ViewController 2 I have some methods to minimise it, but I can't reach navigation controller in ViewController 1 to push other VC there. I tried to do it from window with code:
UITabBarController *mTabBarVC = (UITabBarController *) [UIApplication sharedApplication].keyWindow.rootViewController;
UINavigationController *prevNavigationController = myTabBarVC.selectedViewController.navigationController;
But I had no success I get nil in prevNavigationController.
Create in ViewController 2.h:
#property UIViewController *parent;
In ViewController 1 pass self:
viewController2.parent = self;
In ViewController 2 you can now get UINavigationController:
UINavigationController *navVC = self.parent.navigationController;
It's the best way, because you can change structure of your view controllers in future and will get a bug.
If ViewController 2 present modally on ViewController 1, you can get VC1 from VC2 in self.presentingViewController property.
I have a UIViewController hierarchy that consists of a Root View Controller, which segues to a UITabBarController which has several UIViewControllers under it. Each of those tab UIViewControllers has its own UINavigationController. There is no shared UINavigationController, and none applying to the Root View Controller. Now I have a situation in which I need to pop all the way back to the Root View Controller from one of the tab UIViewControllers. However, since the Root View Controller and the tab UIViewControllers do not share a common UINavigationController, I am unable to simply call [self.navigationController popToRootViewControllerAnimated:YES]. Is it possible to pop to the Root View Controller (or unwind my Segues back to the root programmatically) without a shared, common UINavigationController?
I had a similar problem once, try this
UIStoryboard *storyBrd = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
UIViewController *controller = nil;
controller = [storyBrd instantiateInitialViewController];
self.view.window.rootViewController = controller;
This initial view controller is your RootViewController. Else you can also use the method
[storyBrd instantiateViewControllerWithIdentifier:<View Controller's Restoration ID>]
Here's how I understand the vc arrangement:
|-navvc
| |--rootvc-(pushes)-stack...
"root" vc-(presents)->tabvc--|
|-navvc
|--rootvc-(pushes)-stack...
Its easy to unwind all of this so long as you're holding the handles to the right stuff. The navigation vcs within the tabs can be accessed by any view controller on their stacks with self.navigationController.
You'll need to have ahold on either the tab bar vc or the one you call "root" vc, also. You can arrange that with a property on your app delegate.
The only other tricky thing to remember is that the transition from the "root" vc to the tab bar is a present, not a push, so it must be undone with a dismiss, not a pop. To express this in code, lets say you've got a handle to the tab bar vc on your app delegate....
#interface AppDelegate : UIResponder <UIApplicationDelegate>
#property (strong, nonatomic) UIWindow *window;
#property (strong, nonatomic) UITabBarController *tabBarVC;
- (void)popEverything:(BOOL)animated;
#end
// AppDelegate.m
// get the tab bars tabs (which are presumed to be navigation vcs)
// dismiss the tab bar vc (which was presumed to be presented)
// iterate the navigation vcs, popping all of them to root
- (void)popEverything:(BOOL)animated {
NSArray *viewControllers = self.tabBarVC.viewControllers;
[self.tabBarVC dismissViewControllerAnimated:animated completion:^{
for (UINavigationController *navVC in viewControllers) {
[navVC popToRootViewControllerAnimated:animated];
}
}];
}
Caveats apply: still guessing a little about your vc arrangement, and haven't tested the foregoing.
I've been struggling with a problem lately, and believe me I wouldn't post here if the solution was online somewhere.
Here is my problem :
I have a parentVC with 3 childVC linked to it. I have delegates for the 3 of them to report to the parentVC some actions, and it's working for 2 of them and not the 3rd one even if I declared the same thing for the 3 of use.
Here is my code:
In parentVC.m
// Initializing the clientsListViewController
self.clientsListViewController = [[UIStoryboard storyboardWithName:#"AGZClientsList" bundle:nil] instantiateInitialViewController];
[self addChildViewController:self.clientsListViewController];
[self.clientsListViewController didMoveToParentViewController:self];
self.clientsListViewController.delegate = self;
In my childVC.h
#protocol AGZClientsListViewControllerDelegate <NSObject>
- (void)didClickLeftBarButtonInClientsView:(UIButton*)button;
#end
#interface AGZClientsListViewController : AGZBaseViewController
#property (nonatomic, weak) id <AGZClientsListViewControllerDelegate> delegate;
#end
In my childVC.m
- (void)didClickLeftBarButton:(UIButton*)button
{
if ([self.delegate respondsToSelector:#selector(didClickLeftBarButtonInClientsView:)])
[self.delegate didClickLeftBarButtonInClientsView:button];
}
Please help me!
Is parentVC released before your delegate is invoked? You set the delegate weak, so if the object is free, it will be set to nil.
May be your parentVC is not conforming your childVC's protocol properly. When you the compiler encounters self.delegate, it is trying to find the Controller class who will should conforms to your protocol; basically it is trying to find the delegate class that should implement the methods that you have enlisted inside the #protocol ....#endpart of your childVC class. May be for some reason, it failed.
Let's try a check list-
Have you declared your custom delegate inside the protocol list in the ParentVC. In the parentVC.h class at the interface declaration do
#interface parentVC: UINavigationController
PS: I assumed your parentVc is a navigation controller. It doesn't matter whether it is Navigation controller or just a view controller.
If you have, try moving
self.clientsListViewController.delegate = self;
right after you instantiate the controller, meaning after
self.clientsListViewController = [[UIStoryboard storyboardWithName:#"AGZClientsList" bundle:nil] instantiateInitialViewController];
If still doesn't work then, put a break point after
self.clientsListViewController = [[UIStoryboard storyboardWithName:#"AGZClientsList" bundle:nil] instantiateInitialViewController];
and try printing self.clientsListViewController to see whether you have the instance of your childVc. If it prints nil then there is your problem- your parentVC doesn't have the proper instance of your childVc, so does the delegate.
In that case, try to instantiate the childVC correctly.
I'm currently trying to have a better understanding on how the mechanisms of passing data between controllers work and I'm a little confused especially when passing data back from a second view controller to the main view controller.
This is what I have that works but don't fully understand. I have two view controllers, in the first one I have a button that when clicked it basically goes to the second view controller and a label which shows a message sent from the second view controller. In the second view controller I have a button and a textField, the button basically sends whatever is in the textfield to the label in main view controller.
Here is the code...
// FirstVC.h
#import <UIKit/UIKit.h>
#import "SecondVC.h"
#interface FirstVC : UIViewController <passNames>
#property (nonatomic, strong) NSString* firstNameString;
#property (weak, nonatomic) IBOutlet UILabel *firstNameLabel;
#end
//FirstVC.m
#import "FirstVC.h"
#implementation FirstVC
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier]isEqualToString:#"secondController"])
{
UINavigationController *navController = segue.destinationViewController;
SecondVC *vc2 = (SecondVC*)navController.topViewController;
[vc2 setDelegate:self];
}
}
-(void)viewWillAppear:(BOOL)animated
{
self.firstNameLabel.text = _firstNameString;
}
-(void)setFirstName:(NSString *)firstName
{
_firstNameString = firstName;
}
#end
//SecondVC.h
#import <UIKit/UIKit.h>
#protocol passNames <NSObject>
-(void)setFirstName:(NSString*)firstName;
#end
#interface SecondVC : UIViewController
#property (retain)id <passNames> delegate;
- (IBAction)send:(UIBarButtonItem *)sender;
#property (nonatomic, strong) NSString *firstNameString;
#property (weak, nonatomic) IBOutlet UITextField *firstNameText;
#end
//SecondVC.m
#import "SecondVC.h"
#import "FirstVC.h"
#interface SecondVC ()
#end
#implementation SecondVC
- (IBAction)send:(UIBarButtonItem *)sender
{
_firstNameString = _firstNameText.text;
[[self delegate]setFirstName:_firstNameString];
[self dismissViewControllerAnimated:YES completion:nil];
}
#end
Can someone explain how the prepareForSegue method works in the above code? The reason for this question is because I added an NSLog and it looks like this method is only called in the transition from main view controller to the second controller. Why is this method needed if it is not called when transitioning from second view controller to main view controller which in my case is what I'm doing? It makes sense to use it when passing data from main view controller to a second controller not on the case shown above.
Can some explain the whole mechanism when passing data back to the main view controller?
FYI, I do understand about protocols and delegation.
Thanks a lot.
In your case, you are setting your delegate method of the second view controller to self in mainViewController in you prepareForSegue. This means that apart from navigating to the SecondViewController, you are implementing the callback mechanism in your main view controller, so that your delegate method gets called when the value is passed from the second view controller and this delegate method collects the value as a parameter to handle it in the main View Controller. You might have set the delegate of VC2 as self inn your prepareForSegue because you are creating the instance of VC2 in this method to navigate to the second controller.
Your goal is to hand back the data, like this:
[[self delegate] setFirstName:_firstNameString];
But you can't do that unless you know who to send setFirstName: to, and the compiler won't let you do it unless you guarantee that whoever you are sending setFirstName: to can accept that message.
That is what prepareForSegue prepares. FirstVC has declared that it adopts the passNames protocol, which means that it implements setFirstName:. And now you are saying:
[vc2 setDelegate:self];
...where self is the FirstVC instance. This solves both problems at once. The SecondVC instance (vc2) now has a delegate (the FirstVC instance), it is the right object to send the info back to, and because its delegate is declared as adopting passNames, we know that SecondVC can actually send setFirstName: to that delegate.
Now to the heart of your actual question: The reason for doing this in prepareForSegue is merely that this is the only moment when the FirstVC instance and the SecondVC instance "meet" one another! There is no other moment when the FirstVC instance has a reference to the SecondVC instance so as to be able to call setDelegate on it in the first place. If you weren't using segues and storyboards, the FirstVC would simply create the SecondVC instance directly - and would set itself as its delegate, just as you do:
SecondVC *vc2 = [SecondVC new];
UINavigationController *nav = [
[UINavigationController alloc] initWithRootViewController: vc2];
[vc2 setDelegate:self];
[self presentViewController: nav animated: YES completion: nil];
This is one reason I don't like storyboards: they muddy the story. It's all so simple and obvious when you don't use them and just do everything directly like this.
At the root of my project's world I have a UIViewController *viewController declared in the project's app delegate. I'm trying to get to the view controllers navigationController field so that I can find the top ViewController. However, every time I look at the navigationController field of my viewController it's nil. Clearly I've forgotten to do something. Ideas? Thanks...
Make sure you have a strong property to your navigation controller. (Like Below:)
#property (strong, nonatomic) UINavigationController *navcon;
Then synthesize it in your app delegate implementation file.
#synthesize navcon = _navcon;
And yes, then you can have:
self.navcon = [[UINavigationController alloc] initWithRootViewController:yourViewController];
[self.window addSubview:self.navcon.view];
(Assuming you have ARC enabled)