I am developing an iOS application that handles a hierarchy of UIViewController objects using a UINavigationController:
MenuViewController
|
|-ListOfAnimalsViewController
|
|-AnimalDetailsViewController
|
|-ListOfPlantsViewController
|
|-PlantDetailsViewController
The application receives local NSNotification objects with information of a certain animal or plant. The usual behavior when you touch the notification is to open the application and load the first view controller in hierarchy.
Is there a way of programmatically navigating to an instance of a UIViewController deep in the hierarchy instead?
EDIT: I am not asking for pushing the controller into the navigation stack, but for pushing the previous controllers as well, i.e. I would like to keep the navigation schema above.
I'm gonna have to disagree with Suresh. While this solution may work in a simple case like this where you only have 1 previous ViewController, what if you wanted to add a 10th ViewController and keep the entire hierarchy? First of all you'd have to pass the final piece of data you want to show (in this case the plant or animal) through every ViewController and you'd be creating 10 ViewControllers at once. The transition between the current ViewController and the 10th would be far from seamless, performance would be terrible. Also no need to go and create your own navigation system, no need to make things more complicated than they are since this issue isn't too difficult.
Just push the ViewController you want to show, that way you'll only be creating 1 ViewController. In every ViewController that can be pushed as a result of a notification (and all the ones below that), override the behavior of the back button. Check in the viewControllers property of the navigationController if the ViewController before is is the one you expect, using NSStringFromClass. If not, create an NSMutableArray as a copy of the viewControllers, create the ViewController you do expect and insert it at the second to last spot in the array. Then replace the entire stack by calling the setViewControllers:animated: method on the navigationController with the mutable array, animated NO. Finally do the pop. Again you'll have created just 1 ViewController at a time, keeping your performance optimal.
Ican't post code right now since I'm on an iPad but if you need it, just ask and I'll add an example when i have a real keyboard.
If you know the name of the class you can use the following code to navigate.
NSString *className = #"viewControllerName";
UIViewController* myClass = (UIViewController*)[[NSClassFromString(className) alloc] init];
[self.navigationController pushViewController:myClass animated:YES];
I think you will have to invent your own mechanism for following the navigation order since there's nothing in an iOS view controller's static definition that defines an ordering. (Segues seem to be as close as it gets and that's not very close.)
If I had to do this for a hierarchy of view controllers with some unknown depth, I'd consider creating a superclass that worked with a path, sort of like network routing. It would have a method such as displayNextControllerOnPath:(NSString *)path. A MenuViewController that was a RoutingController subclass would receive a path like #"ListOfAnimalsViewController.AnimalDetailsViewController", strip off the first path element, instantiate it (it being another RoutingController subclass), and send it #"AnimalDetailsViewController".
Other details would involve knowing when to stop and perhaps passing an object or dictionary as a payload for the last controller to use as content.
I would reconstruct the stack in the following way:
MenuViewController *menuController = [[MenuViewController alloc] init];
ListOfAnimalsViewController *listController = [[ListOfAnimalsViewController alloc] init];
AnimalDetailsViewController *detailsController = [[AnimalDetailsViewController alloc] init];
UINavigationController *navigationController = [[UINavigationController alloc] init];
[navigationController setViewControllers:#[menuController, listController, detailsController]];
self.window.rootViewController = navigationController;
You may need to set some properties to based on your implementations.
Related
I embedded 'Core Data' into my app. I use a predicate and fetch the result. I use a mutableArray called "fetchedObjects" to get the (predicated and then fetched) result. Now I need to pass this result to another view controller. How can I do that?
1.First thing I tried is using 'NSCoding' but it didn't work out. I think Core Data doesn't comply with NSCoding, am I right?
If I can use 'NSCoding', how do I do it? I tried to save and then load the fetchedObjects but it didn't work out.
2.Off course I can define a pointer using
"product = [[Product alloc] initWithEntity:entity insertIntoManagedObjectContext:self.managedObjectContext];"
but how can I get just the "fetchedObjects" mutableArray but not all the objects?
3.Do you know a better way to pass a NSMutableArray having NSManagedObjects in it? and how can we pass the NSManagedObjects to the other view controllers, only in the context?
Thanks!
You could use an NSNotification.
Say View Controller A fetches the results, and View Controller B needs them.
in VCA:
NSArray *data = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
[[NSNotificationCenter defaultCenter]
postNotificationName:#"newFetchedDataNotification"
object:nil
userInfo:#{#"data": data}];
in VCB:
in viewWillAppear, start listening for the notification:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(processData)
name:#"newFetchedDataNotification"
object:nil];
and add a method:
- (void)processData:(NSNotification *)notification
{
NSArray *data = [notification.userInfo objectForKey:#"succeeded"]
}
And don't forget to unregister for notifications in viewWillDisappear:
[[NSNotificationCenter defaultCenter] removeObserver:self];
I still think the preferred way is using an NSFetchedResultsController to do a first fetch and be notified when objects change. Take a look at this tutorial:
http://www.raywenderlich.com/999/core-data-tutorial-for-ios-how-to-use-nsfetchedresultscontroller
PS: NSManagedObjects are just like any other objects, no need to use encoding. You need to be careful when modifying and saving them though!
Rather than passing the managed objects directly, if all them are already saved I would pass the ObjectID or the unique url, and have the receiver retrieve them from the store.
Retrieving is fast, and also will avoid many problem that may occur with concurrency.
If the receiving controller just need to display data, I would also think about retrieving just the properties you need with an NSDictionary result type, and pass the resulting array to the controller. But of course, I don't know anything about your design.
UPDATE
If I understood correctly your comment, the big advantage of NSFRC is the bunch of delegate methods it brings with it. If you made your UIViewController the delegate of your NSFRC which is NSFetchedResultsControlerDelegate then the fetched controller itself will invoke your view controllers implemented delegate methods, as soon as there's a change in the model. Then within this methods you have to refresh your table view.
The view is not going to be refreshed by itself.
The difference is that if your UITableView datasource is just an array of managed objects, you would need to build by yourself all the logic to react to model change, recognize which cell need to be refreshed, then decide if it deleted/updated/moved etc.. if you think about it, it is quite a few logic to be implemented, and will not be as efficient as the NSFRC is which is already customized for this purpose.
Also, by reading the rest of your comment, you cannot pass a NSFetchedResultsController, well I suppose you can, but it is useless, the NSFRC is meant to be created and used in the UIViewController on screen. The NSFRC is instantiated with few parameters, one of them is a NSFetchRequest, so you have to customize your request to retrieve the objects you need.
P.S.
Sorry, at the moment I am behind a company firewall which is blocking many sites (including Apple docs), therefore I cannot give you any links.
In simple way ,
VC2.h
#property (strong) NSMutableArray *device;
VC2.m
#synthesize device;
Now in Your VC1 from which you want to pass array
VC2 *v2=[VC2 alloc]initWithNibName:#"VC2" bundle:nil];
v2.device = array;
[self.navigationController pushViewController:v2 animated:YES];
or if you are not using Navigationcontroller.
[self presentViewController:v2 animated:YES completion:nil];
I am using Xcode 4.3 and need to know the parent view controller of the current view.
I am also using storyboard.
self.parentViewController always returns nil.
I saw different answers to save the parent view controller in AppDelegate class as a property. E.g., To create a variable: UIViewController parentViewController in AppDelegate and in the code write:
appDelegate.parentViewController = self;
I am afraid that it will consume memory - as this is a heavy object.
What is the best approach to know aretnViewController when using story board?
Whether or not an object is "heavy" does not matter as long as you store only a reference to it (in your case in the application delegate). Creating a second object would make a difference, but the line
appDelegate.parentViewController = self;
does not do that, it merely stores a reference to the object.
I know that this does not answer your direct question, but I think you should go ahead with the "store a reference in the app delegate" approach.
I am new with Objective-C so apologies for a dumb question.
I am opening an "options" view controller from my main view controller. Both are built in the storyboard. Before and after presenting the options controller I need to stop and start a timer on my main view controller. After the options controller is closed (a button calls dismiss) I need to send some info back to my main controller or at least let my main controller know that it needs to refresh some values.
MAIN QUESTION
What's the best way of presenting a view controller and executing some presenter's methods before and after opening?
WHAT I'VE TRIED
I found a few ways to do it, but they are all cumbersome and I assume that there must be some plausible way of doing it.
Ideally I'd like to use the segue I set up in the storyboard between the two controllers.
I managed to call the options controller programmatically by accessing the storyboard and calling instantiateViewControllerWithIdentifier. It worked but looks a bit complex.
I was not able to find a delegate method on the UIViewController to handle the dismiss event
When I was trying to access the main controller in the options controller via presentingViewController and downcasting it, I got a linkage error by including my .h file twice (not sure what are the Obj-C standards of using #define).
Appreciate your help...
For communication between ViewControllers that are weakly linked, you could use the NSNotificationCenter:
https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSNotificationCenter_Class/Reference/Reference.html
Here you can send a message to all ViewControllers listening, which need to process some changes (for example an option to change the font size).
It's really easy to implement and it keeps certain ViewControllers less dependent on each other.
All of this can be done quite easily with storyboard and NSNotificationCenter, and NSCoding. In the viewDidLoad method of your main controller, put this code:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(receiveNotification:)
name:#"Update"
object:nil];
Then create this method in the same controller:
(void)receiveNotification:(NSNotification*)notification
{
//...
}
When you want to make the main controller update from the options controller:
[[NSNotificationCenter defaultCenter] postNotificationName:#"Update" object:self];
Also, I would suggest using NSArchiving for Basic Data Persistence. Just found this tutorial, looks pretty good.
http://samsoff.es/posts/archiving-objective-c-objects-with-nscoding
Basically, create an object that can store information, code it using nscoding, and then uncode it whenever you need it. It has worked great for me.
Hope that helps!
MAIN QUESTION What's the best way of presenting a view controller and executing some presenter's methods before and after opening?
Just in case the answers above are a bit more involved than you'd like, I'll suggest that the easiest way to execute a presenter's methods before opening is to do so in the presenter's prepareForSegue method. If you need to send data to the destination view controller, you can access its properties this way:
ViewController *destinationVC = [segue destinationViewController];
An easy way to execute the presenter's methods after opening would be:
ViewControllerSubclass *previousVC = [self presentingViewController];
And then use the class or instance to execute your class or instance methods. You could do this in the destination's viewWillAppear.
Sorry if you already knew all this; it's often difficult to surmise what level of complexity is needed.
I have run into this with almost every app I have on the market. Difference is I have never decided to go down the storyboard path.
The way I have always been able to accomplish this is to provide accessor functions between the controllers. You get past the linker issue by defining the cross defined controller as simply a UIViewController type within your options view header, then including the main view controller' header only in the .m file. Now when you call a main view controller routine from your options view, you will have to cast it to the type of your main view controller!
You will also have to provide a routine in your options view that will allow you to set the variable that will hold a pointer to your main view controller to self.
Example for your optionsView
#interface optionsViewController : UIViewController{
UIViewController * myReactiveMainViewController;
}
-(void)setMyReactiveMainViewController:(UIViewController *)controller;
No in the .m file for the optionsView
#import "myMainViewController.h"
-(void)setMyReactiveMainViewController:(UIViewController *)controller{
myReactiveMainViewController = controller;
}
In any other call back to the main view controller you will have to do this:
-(void)returnToMain{
[(myMainViewController *)myReactiveMainViewController someCall:variable];
}
This example would of course assume that your myMainViewController implements a method called "someCall" that take on input parameter.
Thanks for replies.
I ended up with
Calling prepareForSegue to execute pre-transition code
Calling performSelector on presentingViewController when releasing presented view controller.
I am sure other suggestions would work too.
I am still new to iOS development and I wanted to know how to accomplish something. What I have is s UIViewController objects FirstController and SecondController. In the NIB file for FirstController I have a UITextField. In the NIV file for SecondController I have a UILabel.
What I want to do is update the UILabel with a new value as I update the UITextVew. So far, I have the following in my FirstController:
- (IBAction)tbxName_EditingChanged:(id)sender;
In the SecondController I have the label in an Outlet Collection like so:
#property (retain, nonatomic) IBOutletCollection(UILabel) NSArray *lblName;
Now, I have made the collection because I intend, in the future, to add more labels that will need to be changed and have all have the same value.
So, what I was thinking that I have to do is place a reference of my SecondController into my FirstController so that I can then run a custom 'update' method. Something like this:
FirstController *viewController1 = [[[FirstController alloc] initWithNibName:#"FirstController" bundle:nil] autorelease];
SecondController *viewController2 = [[[SecondController alloc] initWithNibName:#"SecondController" bundle:nil] autorelease];
viewController1.secondView = viewController2;
// do the rest to load the views...
Now, I have learned that a lot of the stuff I have learned, in .net., is not the way you do things in Objective C. I was wondering if this is what I have to do, or is there another way that I am not finding with Google?
Thanks for any help, and I hope that I explained this clearly enough.
Solving problems like these in a predictable, proven way is the whole point of the Model-View-Controller design pattern. The point is, you want the first UIViewController to update the model, which will signal the second UIViewController to update its view.
I'd suggest having a model somewhere. Either by using a singleton, first initialized in the app delegate, or core data, which is a little more advanced but very powerful.
I'm creating a custom ViewController. This VC needs to load some data that is known in the event that creates it and pushes it to the top of the NavigationController that it is going to be part of.
My question is, how should I pass data from the view that handles the custom ViewController's creation into that custom ViewController.
I've thought of four possible options, and I was hoping to get feedback on why each one is good or not for this functionality.
1) Expose public properties in the custom ViewController and set the UI elements in the view based on those properties in - (void) ViewDidLoad.
2) Expose the actual UI elements themselves and set their .text/.image/.whatever attributes as the ViewController is being created.
3) Create a custom constructor for the custom view and pass in the values I need to set up the UI elements
4) Create a custom model that both views have access to, set the data before the CustomView is created/pushed, and access that data in the ViewDidLoad event.
I'm still new to all of this, and I want to make sure that I understand the proper handling of these handoffs of data. It seems like something like this is probably a simple answer, but I'm still a little confused and its probably really important to do this right to avoid memory loss/leaks.
Also, in case anyone cares, I'm using Stanford's CS193p class on iTunes U and Mark/Lamarche's "Beginning iPhone Development" to teach myself cocoa for the iPhone. I'm working on an application with a NavigationController and a couple ViewControllers (Presence 1 if you're familiar with 193p).
Well, I believe there are advantages & disadvantages to each of those methods depending on your requirements...often it will require some combination of approaches. I believe the most common, for me anyway, is to do something like this where you give it enough to get started.
MyViewController *vc = [[MyViewController alloc] init]; // (or initWithNibName:bundle:)
// transfer vc values here
vc.value1 = aValue;
vc.value2 = anotherValue;
[self.navigationController pushViewController:vc animated:YES];
[vc release];
After your view controller is instantiated you have an opportunity to pass objects to it. Say MyViewController is a detail view then you'd give it the object it will be displaying the details for. Or, if it's a table view you can give it the NSArray it will need for display. Then in viewDidLoad or awakeFromNib or awakeFromCoder, or... you can fill out the view...so to speak.
#1 is fine, with or without #3 (these two are not mutually exclusive)
#4 is my preferred solution. For instance, if I had a UserViewController, I would probably also like to have a User object and create it this way:
User *user = [self.users objectAtIndex:someIndex];
UserViewController *uvc = [[[UserViewController alloc] initWithUser:user] autorelease];
#2 is not a good idea. Objects should not access the UI elements of other objects. Much trouble comes from this when you decide to change your UI around (and you will).