How can I get a reference to the current UINavigationController? - objective-c

In Objective-C, what is the best way of getting a reference to the current UINavigationController? I would like to gain access to it from any class that may not have a reference to a UIController, delegate, or anything else.
Is there an existing way to get the current UINavigationController? Should I add something to my application delegate, and retrieve that?

As it turns out, you can get it, albeit with quite a bit of dot syntax, from any UIView/UIViewController descendant.
self.(view).window.rootViewController.navigationController;
If you are using an object that descends from any other class, the same is true, except you must go through UIApplication
[UIApplication sharedApplication].keyWindow.rootViewController.navigationController;
Of course, both of these break MVC in a big way, and it is definitely recommended that any logic that must touch the "default navigation controller" be added to the root view controller.

Swift 4:
guard let navigationController = UIApplication.shared.keyWindow?.rootViewController as? UINavigationController else { return }

Related

Global Access to Class instance - Optimal design approach?

OK, here is my situation and I'm really not sure on which design approach to use. So, I'd be glad to read some input on my particular case...
The scenario :
I've got a tab-based application
Initially we create an instance of NSWindowController (e.g. MyDocumentManager) which takes care of the tab creation/manipulation
Each tab contains (= is attached to) an instance of MyDocument
Now, I'm about to implement the menu actions.
For each menu there is a separate Menu Controller (actually a subclass of NSObject grouping all relevant functions), e.g. MyFileMenuController
File Menu's actions are linked to actions in a MyFileMenuController object, via Interface Builder
The question :
How is it possible that MyFileMenuController "knows" about MyDocumentManager (created in my AppDelegate.m), so that we can access current document details and perform all relevant actions? Any ideas? Which approach is preferable?
My ideas :
Passing object from class to class (not sounding that great)
Singletons (although I've honestly never used them, and do not know whether/how it could be my particular case)
Notifications & Notification Listeners
(Looking at it from the opposite side, though not sure) Delegate methods
OK (not sure if that's the best way to do it), but this is what I decided to do :
[[[NSApplication sharedApplication] delegate] MyDocumentManager]
So, as long as an object is part of my AppDelegate, this way I can access it from anywhere.
I would make an initial UIViewController linked to appDelegate.rootViewController.
In this new view controller (just call it "mainViewController") I would put my tabBar and the menuController.
I understand your interface is similar to facebook with a leftBarbutton which makes horizontal scroll and discover the menu. So in the selector for this leftBarButton I would call a method like:
- (void)discoverMenuForDocument:(MyDocument*)document {
// Set menu configuration for specific document
// Make animation to discover menu
}
Where document could be something like:
self.selectedViewController (<-- You cand make this in several ways depending on your code...)
Realize that (MyDocument*) is not an object but just a reference, so in my opinion there isn't any problem doing this.
Good luck!

Saving UIViewController in appDelegate

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.

Communication between UIViewControllers

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.

iOS: Uniquely identify ViewControllers from Storyboard

I have a custom ViewController that is meant to be reusable, and an arbitrary number of instances will be chained together in a NavigationController in Storyboard, all sharing the same model as a delegate.
The ViewControllers need to tell the model which instance they are. Currently, they have an int property that they get from the segue, but it doesn't seem very idiomatic and doesn't lend itself to having multiple instances onscreen (for iPad). I figure there's got to be a cleaner way to do this, so does anyone know what it is? Thanks.
RESULT: self.view.tag
A UIViewController's UIView has a tag property which you can set from anywhere you want. You could also simply identify the type of the controller by using [self class]. Or simply use the memory location by referencing the controller directly.
Update You could simply implement a unique identifier for a UIViewController using a category.
I guess the "cleanest" way in terms of design architecture would perhaps be an array of ViewControllers. (It could be managed in the app delegate.) However, there are memory considerations - on the iPhone you would likely want to create and the destroy the view controllers as needed. The array could contain the identifier and perhaps some other model-related information in order to recreated the controllers as needed.
Too bad there is no property storyboardIdentifier of UIViewController. They can be instantiated with this id but it would be helpful if the viewcontroller can query its id.
I recently ran into this. I figured out you can add a "Restoration ID" in the storyboard. Then you can access it perhaps like this (depending on your use case)
navigationController?.viewControllers.first?.restorationIdentifier

Traversing the ViewController hierarchy properly?

I'm having trouble referencing one view controller from another. The code works but I get warnings which makes me think I'm going about it wrong. I'm trying to reload the data in a tableView whose controller is in a NavigationController.
What's wrong with a message like this:
From the AppDelegate:
[self.tabBarController.selectedViewController.topViewController.tableView reloadData];
Although this works, I get the warning request for member 'topViewController' in something not a structure or union because Xcode doesn't know that the selectedViewController will return a navigationController. So I could do the following:
UINavigationController *myNavigationController = self.tabBarController.selectedViewController;
[myNavigationController.topViewController.tableView reloadData];
But then I get this warning: incompatible Objective-C types initializing 'struct UIViewController *', expected 'struct UINavigationController *'
How far do I have to go with this? The first line works. To get to the "right way" is it gonna take 8 lines of code?
A major code smell here, IMO. You're trying to do action at a (great) distance. It's not exactly clear what you're trying to accomplish, nor why you need to do this action from the app delegate. I have seen some developers treat the app delegate like a giant catch-all global lump of mud, and I think this is an anti-pattern that should be eliminated from iOS development.
Back to your question: you're trying to force a table view controller, inside a tab view controller, to reload its data. I'm assuming this is in response to something happening. Why not have the view controller in charge of that table watching for that event instead of the app delegate? That way, the thing that owns the table view is directly controlling it -- which is the entire point of the MVC pattern. This is a much better approach than having the app delegate drill down through a hierarchy to find a table view... in terms of complexity, readability, and brittleness.
If, for some reason, you can't or won't have that view controller observing for the event directly (hard to fathom why offhand), you could always have the app delegate post an NSNotification and let the view controller in charge of the table register as an observer for it. Not as good as direct observation, but definitely better than your current approach.
You can't use dot-notation unless the compiler knows what type of object you are using it on, and that that object type can receive a message with that name.
You can use dot-notation with a bunch of type-casts (which in this case, is hideously ugly):
[((UITableViewController *) ((UINavigationController *) self.tabBarController.selectedViewController).topViewController).tableView reloadData];
Or you can break it up into discrete steps:
UINavigationController *navController = (UINavigationController *) self.tabBarController.selectedViewController;
UITableViewController *tableViewController = (UITableViewController *) navController.topViewController;
[tableViewController.tableView reloadData];
Note that I'm assuming that your top VC is a sub-class of UITableViewController.
You really shouldn't be accessing the .tableView property externally - you should encapsulate that behaviour with a reloadData method on the View Controller itself. Even if all it does is call reloadData on its .tableView, you should encapsulate it. This will make your code more modular (which makes it easier to understand for you and others), and make it easier to expand on and add complexity to your View Controller down the track.
Without knowing exactly how this app is structured, I would guess that you're probably better off using notifications or observers to get your VC to reload its data. If you have some global event that requires a UI refresh, an NSNotification is a good way to make the UI layer get the message while keeping your code nice and modular.