Changing properties of an open ViewController via NavigationController stack - iphone

Wondering how I can set properties of view controllers that are already on the NavigationController's stack
My situation:
I want to set up an image uploading flow like this
(Navigation Stack)
RootViewController -> TakePictureViewController -> EditPictureViewController -> UploadPictureViewController
When user confirms the upload from the UploadPictureViewController, rather than start to upload, I want to set an NSDictionary property on RootViewController which contains the upload query, then pop the navigation stack back down to the RootViewController and have it handle initiating and status reporting of the query.
Here's my code in the uploadpictureviewcontroller, currently, the code does pop to the right view controller, but the uploadPackage property is still nil, also I have tried to -setUploadPackage
RootViewController *rvc = (RootViewController *)[self.navigationController.viewControllers objectAtIndex:0];
rvc.uploadPackage = uploadPackage;
[self.navigationController popToViewController:rvc animated:YES];
All help appreciated, thanks.

try using [self.navigationController popToRootViewControllerAnimated:YES]. That should do it.
EDIT:
If you have only one instance of RootViewController, then you can set it up as a singleton and therefore you can access it from any other controller (just like the appDelegate). To do so you need to add the following to your RootViewController.m under synthesize...; :
static RootViewController *rootViewController;
+(id)sharedRootController {
return rootViewController;
}
inside your init method for RootViewController add the following line:
rootViewController = self;
now back to your UploadPictureViewController you can set the uploadPackage like this:
RootViewController *rvc = [RootViewController sharedRootController];
rvc.uploadPackage = uploadPackage;
Please note that you should NOT use the singleton method if there is to be more than one instance of RootViewController.
hope this helps!

Related

XCode5 self.navigationController pushViewController:animated: doesn't open the new viewcontroller

I'm not sure why but in Xcode 5 working on a project of IOS6.1 I have a button connected to a IBAction in which I'm trying to navigate to a new view controller.
I've tried two different codes to create the viewController and then push it to the navigation in both cases the view controller is not nil and both cases the viewController doesn't appear.
first try: with story Id - I've set the story id of the view controller to imageCapture and set the class to VSImageCaptureViewController
VSImageCaptureViewController* imageCaptureViewController = (VSImageCaptureViewController*)([self.storyboard instantiateViewControllerWithIdentifier:#"imageCapture"]);
[self presentViewController:imageCaptureViewController animated:NO completion:nil];
second try: with the name of the viewcontroller
VSImageCaptureViewController *imageCaptureViewController = [[VSImageCaptureViewController alloc] initWithNibName:#"VSImageCaptureViewController" bundle:nil];
[self.navigationController pushViewController:imageCaptureViewController animated:YES];
can you see something wrong or do you think I forgot to initialize something
Check to see if self.navigationController is nil.
If it is nil that means that you are not running within the context of a UINavigationController (the system sets this property for you when the UIViewController is added to a nav stack).
If this is the case then you have not properly set up a UINavigationController.
Note that you can not set the navigationController property yourself. The systems sets it for you when the UIViewController is added to a UINavigationController's stack (and sets it to nil when it is removed from the stack).
To set this up you will usually create a UINavigationController instance right after you create your main view controller.
UIViewController *mainViewController = ...;
UINavigationController *mainNavController = [[UINavigationController alloc] initWithRootViewController:mainViewController];
// now present the mainNavController instead of the mainViewController
If you are using storyboards you would drag out a UINavigationController instance and replace the default root view controller with an instance of your mainViewController.

how to access previous view elements in UINavigationController

I have a UINavigationController in which I am loading different view controllers. I want to know how can i access the elements (like labels etc) of my previous view.
Here is an eg.
View A
myLabel.text = #"first view";
(User moves to view B)
View B
(user entered a message, that i need to display in View A)
something like ViewA.myLabel.text = #"user entered message"
I tried many things but was not able to find anything very useful. Please help..
I am using Xcode 4 without ARC and without storyboard.
Thanks
Sam
Edited:
I want to update the property declared in viewController of View A and not the labels directly. My labels get updated using that property. Like while pushing the viewController we can pass the values as below.
ViewA *myView = [[ViewA alloc] init];
myView.title = #"View B" ;
myView.tableView.tag = 3;
myView.myTextView.text = #"Some Text";
[self.navigationController pushViewController:myView animated:YES];
[myView release];
Is there any way to pass these values to properties of ViewController of ViewA while popping ViewB and returning back to ViewA ?
The actual scenario is as follows: the user gets and option to write a message in textView or he can use the predefined templates. If he clicks on the templates button he is taken to a list of predefined templates where he can select any of the predefined message. Now I want that when the user click on any of the predefined message the view containing the list of predefined message gets popped of and the message he selected gets automatically populated in the textView of main view. what is the best approach to achieve this ?
TIA
Sam
You should set your AViewController as the delegate of your BViewController so you can message it back after a particular event. Using a delegate will also allow better decoupling of your ViewControllers.
In your BViewController, define a protocol like this :
BViewController.h :
#protocol BViewControllerDelegate <NSObject>
- (void)viewB:(UIViewController *)didEnterMessage:(NSString *)message;
#end
and add a delegate property :
#property (nonatomic, weak) id <BViewControllerDelegate> delegate;
When the user enter the message in your BViewController and hit the button that pops the BViewController to show to AViewController do this :
- (IBAction)messageEntered {
if ([self.delegate respondsToSelector:#selector(viewB:didEnterMessage:)]) {
[self.delegate viewB:self didEnterMessage:self.yourTextField.text];
}
}
Your AViewController should implement the BViewControllerDelegate protocol like this :
AViewController.h :
#interface AViewController <BViewControllerDelegate>
When your AViewController creates the BViewController, it should set itself as its delegate before presenting it. Might look like this :
BViewController *bvc = [[BViewController alloc] init…];
bvc.delegate = self;
And finally, your AViewController should implement the viewB:didEnterMessage: method :
- (void)viewB:(UIViewController *)didEnterMessage:(NSString *)message {
self.myLabel.text = message;
}
That's the cleanest way to do that, IMHO.
You can get the navigation controller's viewControllers property and use it, perhaps like this:
UILabel *label = ((SomeViewController *)[self.navigationController.viewControllers objectAtIndex:1]).myLabel;
However, that is not reliable. Since the “previous” view is off the screen, the system can unload it to free up memory. Then label will be nil.
You could force that other view controller to reload its view (if it has been unloaded) by accessing the view controller's view property.
But really this smells like bad design. You should almost never try to access the views of a view controller when that view controller's view is not on screen. Remember how the system can unload a view controller's view if the view is off-screen? If some UILabel under that view contained the only copy of important data, that data is now gone!
Any important data needs to be stored somewhere other than a view - perhaps in a property of the view controller, or in a model object. You should ask the view controller for the data, or for the model object that contains the data. A view controller's view objects should almost always be considered a private implementation detail of the view controller, not exposed to other classes.
EDIT
Your question is puzzling because you talk about popping ViewB and returning to ViewA, but your code only creates and pushes a ViewA. ViewB is not mentioned in the code.
I will assume that your ViewA creates and pushes a ViewB. So you should give ViewB a property of type ViewA, like this:
#class ViewA; // forward declaration to avoid circular imports
#interface ViewB
#property (weak, nonatomic) ViewA *aView;
Then, when your ViewA creates a ViewB instance, you set the aView property:
#implementation ViewA
- (void)pushViewB {
ViewB *bView = [[ViewB alloc] init];
bView.aView = self;
[self.navigationController pushViewController:bView animated:YES];
}
Now your ViewB has access to the ViewA that created it, and can set the properties of that ViewA.
If you want to write a good code you should follow the Model-View-Controller pattern. Here's rather good tutrial http://www.cocoalab.com/?q=node/24 In a couple of words it means that you should not store data in View (and also a view should not act as controller). I suggest you to write a custom class that will do this management(store data and pass it from one view to another).
If it's just a test app then you can use viewControllers property of UINavigationController to access the controllers which are in navigation stack or just create a variable to store this data for example, in View B
- (void)textFieldDidEndEditing:(UITextField *)textField {
stringToDisplayInFirstController = textField.text;
NSArray * arrayOfControllers = self.navigationController.viewControllers;
UIViewController * viewControllerA = [arrayOfControllers objectAtIndex:[arrayOfControllers count]-1];
viewControllerA.label.text = stringToDisplayInFirstController;
}

Pass managedObjectContext (Core data) to other classes, correctly done?

I used the default template provided by Apple with Core Data (managedObjectContext is in AppDelegate). At first I was including appdelegate.h in every classes I needed to use managedObjectContext, but I saw this was not the correct way to do it. Apple says it's better to only pass the context to other classes that need it and so on, so I ended up doing it that way. Thing is, it looks a bit "hackerish" the way I did it, and I'm wondering if there's a better option or my solution is correct.
My app is currently setup like that (here's a SS of my storyboard):
So my root window is a UITabBarController, and each tab is a UINavigationController that points to multiple UITableViewController/UIViewController.
Here is what I have in my Appdelegate to pass the managedObjectContext instance to 2 tabs:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
UITabBarController *rootViewController;
UINavigationController *navigationController;
ItemsTableViewController *itemsTableViewController;
// Get the root window (UITabBarController)
rootViewController = (UITabBarController *)self.window.rootViewController;
// Get the second item of the UITabBarController
navigationController = [[rootViewController viewControllers] objectAtIndex:1];
// Get the first item of the UINavigationController (ItemsTableViewController)
itemsTableViewController = [[navigationController viewControllers] objectAtIndex:0];
itemsTableViewController.managedObjectContext = self.managedObjectContext;
// Get the third item of the UITabBarController (again ItemsTableViewController)
navigationController = [[rootViewController viewControllers] objectAtIndex:2];
// Get the first item of the UINavigationController (ItemsTableViewController)
itemsTableViewController = [[navigationController viewControllers] objectAtIndex:0];
itemsTableViewController.managedObjectContext = self.managedObjectContext;
return YES;
}
Everything works well, but having to call multiple times objectAtIndex to get to the right ViewController looks meh...
Anyone as a better solution?
Thanks!
You should look at using the prepareForSegue: method to pass your managedObjectContext to the other controllers.
Alternatively, you can subclass the tab bar controller and add the managed object context as a property, which you can then access from anywhere within your app provided the tab bar controller is also there.
Finally, if you are only ever going to use one context (i.e. no multi threads) you can always setup a CoreDataHelper class with a class method that returns your default context whenever you ask for it. To avoid importing the helper in every single class just add the helper to your precompiled header file (.pch) and let it also import the <CoreData/CoreData.h> framework.
If you want to see an example of how this is done, checkout MagicalRecord on github https://github.com/magicalpanda/MagicalRecord
[EDIT]
Here's an example of how you would pass the context using the prepareForSegue method. Remember that this method is called when a segue is about to initiate and it gives you the opportunity to setup the view controller that is about to be pushed. This is where you could pass delegate references and assign values to other variables in your destination view controller:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
NSString *segueIdentifier = [segue identifier];
if ([segueIdentifier isEqualToString:#"YourSegueIdentifier"]) // This can be defined via Interface Builder
{
MyCustomViewController *vc = [segue destinationViewController];
vc.managedObjectContext = self.managedObjectContext;
}
}

Prevent viewcontroller being reset - UINavcontroller + Storyboard + Segue's

I need some help in picking the 'right' solution for the following 'problem'.
I use the new storyboard feature to link all the screens of my application together. Basically the structure drills down to:
[Navigation Controller] => [View Controller #1] => [Tabbar Controller] => [View Controller #2]*
**(and some other tabs which are for now not important)*
I have attached a segue (push) from the first View Controller (#1) to the View Controller behind the Tab Bar Controller. This push is triggered when the users presses something on the first controller and works fine.
// Execute preset segue
[self performSegueWithIdentifier:#"segueEventDetail" sender:self];
When the user (which is now in the View Controller #2) presses the back button in the navbar the user goes back. Suppose he now triggers the segue again, the second view controller is shown again but is now 'resetted' (empty). (I believe after reading several fora and articles this is standard behavior when using segue's because these destroy and reinitiliaze the view controller's every time?)
This (the view controller being resetted) poses a problem because the contents of the second view controller is dynamic (depend on a JSON response from the server) and thus it is 'needed' that the view controller remains intact (or is restored) when the user comes back.
I have found several sources (see bottom) describing the same issue, but the solutions vary and I need some help picking the right one.
Summarize:
How can I 'retain'/save the state of a View Controller when the users presses back, while preserving the use of Storyboard & preferably also Segue's
Own Thoughts:
#1 I'm now thinking of caching the JSON Response to my singleton class (and from there to a PLIST) and checking within the second view controller if this data is present and than rebuild the view after which I check for any new data (resume normal operation).
#2 Another one I'm thinking of is 'bypassing' the segue and manually handle the switch of views , partially explained in (Storyboard - refer to ViewController in AppDelegate) - Is this also possible?
But maybe there is an easier/better option?
http://www.iphonedevsdk.com/forum/iphone-sdk-development/93913-retaining-data-when-using-storyboards.html
Storyboard - refer to ViewController in AppDelegate
How to serialize a UIView?
Yess!! I got the solution. Do the following:
In you're .h file:
#property (strong, nonatomic) UITabBarController *tabController;
In you're .m file:
#synthesize tabController;
tabController = [self.storyboard instantiateViewControllerWithIdentifier:#"tabbar"];
The selected index is the tab you want to go
tabController.selectedIndex = 1;
[[self navigationController] pushViewController:tabController animated:YES];
For anyone coming across this (my) question in the future, this is how I ended up 'coding' it.
Open the storyboard and select your 'Tab Bar Controller' and open the Attributes Inspector
Fill in an 'identifier' in the field
With the first view controller (see scenario in original post) I create an global reference to the viewcontroller:
firstviewcontroller.h
#interface YourViewController : UIViewController {
UITabBarController *tabController;
}
firstviewcontroller.m
//Fill the reference to the tabcontroller using the identifier
tabController = [self.storyboard instantiateViewControllerWithIdentifier:#"tabbar"];
Now to switch from the firstviewcontroller the following line can be used:
[[self navigationController] pushViewController:tabController animated:YES];
This might be even more simple solution (without using properties - in fact, all your class instances don't need to know about their destination controllers, so just save it as static in the pushing function):
static UIVewController *destController = nil;
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
if (!storyboard) {
DLog(Storyboard not found);
return;
}
if (destController == nil) { //first initialisation of destController
destController = [storyboard instantiateViewControllerWithIdentifier:#"{Your Destination controller identifyer}"];
if(!destController) {
DLog(destController not found)
return;
}
}
//set any additional destController's properties;
[self.navigationController pushViewController:destController animated:YES];
p.s. DLog is just my variation of NSLog.
But it's really interesting how to do this with segue?

self.navigationController pushViewController not working

I have a View application with a Single UIViewController. I then add a UITableViewController through the IB, and I am trying to display the UITableViewController through a button press in the UIViewController (my main view). My button press (IBAction) contains the following code through which I am trying to push my UITableViewController view and display it:
DataViewController *dataController = [[DataViewController alloc] initWithNibName: #"DataViewController" bundle:nil];
[self.navigationController pushViewController:dataController animated:YES];
[dataController release];
My DataViewController is not at all getting pushed into the stack and displayed,
Also I have checked that in the code above, self.navigationController=nil
Probably this is the source of the problem. If so, how to rectify it?
Please help.
UINavigationController *navCtrlr = [[UINavigationController alloc]initWithRootViewController:yourfirstviewController];
[self.window setRootViewController:navCtrlr];
navCtrlr.delegate = self;
navCtrlr.navigationBarHidden = YES;
Create navigation controller in appdelegate.m then you can navigate to any uiviewcontroller
You need to actually create a UINavigationController. The navigationController property tells you whether your DataViewController is currently in a UINavigationController's hierarchy; if not (as in this case), the navigationController property returns nil.