UINavigationController subclass - customizing Pop - objective-c

Like many others, I would like to perform an action when the 'back' button is used in a UINavigationController. I am aware of some alternative approaches to this, such as adding my own button and trying to spoof the look, using viewWillDisappear and setting tags on all non-back actions that will cause the view do disappear, etc.
But the one that I would like to try is subclassing UINavigationController and injecting something in popViewControllerAnimated:
But I can't get a simple test case to work. Here is what I tried...
I created a new class, CustomNavigationController, which inherits from UINavigationController. In here, I updated popViewControllerAnimated:
.h:
#import <UIKit/UIKit.h>
#interface CustomNavigationController : UINavigationController
#end
.m:
#import "CustomNavigationController.h"
#interface CustomNavigationController ()
#end
#implementation CustomNavigationController
- (UIViewController *)popViewControllerAnimated:(BOOL)animated{
NSLog(#"pop!");
return [super popViewControllerAnimated:animated];
}
#end
In interface builder, I changed the class of the navigation controller:
What am I missing? I am expecting 'pop' to be logged whenever I pop any view within this nav controller.

I've just tested the very same implementation on iPhone 5.1 simulator and it works fine - it seems the problem is somewhere else.
I've created an empty project from a single-view application template and added CustomNavigationController class with the code which you posted in the question body. An UIButton is used to push another view controller to navigation stack (using storyboard's segue), and 'POP' message is being posted in the console as expected.

From the first paragraph of the documentation on UINavigationController.
The UINavigationController class implements a specialized view
controller that manages the navigation of hierarchical content. This
class is not intended for subclassing. Instead, you use instances of
it as-is in situations where you want your application’s user
interface to reflect the hierarchical nature of your content.
If you want this kind of customization you're better off implementing your own navigation style controller.

Related

Accessing an instance method of a ViewController From Another view controller

Let's say I have a view controller called vc1, which a synthesized property called property1, and i wants to access it from another view controller (vc2) and change it from vc2.
Now the methods created by the #syntisize to change and get properties are instance methods, so how can I get to them fro another view controller (do view controllers have instances in the app, and if so, what are they?)
Just to be clear I am using storyboards, so I never really instantiate the view controllers...
VC1.m:
-(void) yourMethod {
...
}
VC2.m
YOURViewController * vc2 = [[YOURViewController alloc]init];
[vc yourMethod];
[vc release];
Make sure to import your YOURViewController in your other view .m file
Something like that should work.
Or if you're having problems, try this tutorial here:
Tutorial on How-To Pass Data Between Two View Controllers
Hope this helps :)
While you can do it the way you describe, I think the common technique (assuming VC1 has a segue to VC2) is a bit different, where VC2 will have a property that will be set by prepareForSegue. See Configuring the Destination Controller When a Segue is Triggered in the View Controller Programming Guide.
You will need to link the storyboard views with the viewcontrollers so the view for vc1 would use the class vc1 etc for the rest (I assume you have done this because this is important when coding for different views)
Then all you need to do is where ever you are calling the properties so lets say the viewDidLoad method, declare the view controller like this:
- (void) viewDidLoad {
vc1 *viewController;
// Now you change the variable I'll presume its a UILabel so I'll change its text [viewController.property1 setText:#"I changed a different views UILabel"];
}
Let me know whether this works... Its worked for me before so should work

Dismissing the split view popover controller

I have a UISplitViewController with the master view set up like this:
UITabBarController
Tab1:
UINavigationController -> UIViewController -> UIViewController
Tab2:
UINavigationController -> UIViewController
Each of the UIViewControllers is a table view, and when the user chooses a row in the last one, an image is shown in the detail view, which contains a UIScrollView.
The tab bar Controller is the UISplitViewControllerDelegate and handles putting up the button on a toolbar at the top of the scroll view.
The problem is, I want to add code to dismiss the popover when the user makes their choice. The pointer to the popover has to be saved in the tab bar controller when the button goes up, and then used to dismiss the popover several view controllers down the line when the user makes their final selection. There doesn't seem to be any way for the view controller that needs that pointer to get at it, without doing something gross like storing it in the App Delegate.
I don't see other people asking this question, which leads me to believe that I've once again overlooked something simple. Please enlighten me!
It sounds like your tab bar controller is already a subclass of UITabBarController, which means that you've already got some custom code in there. I would suggest that the tab bar controller is the primary owner of the popover, and it is the table view controller's responsibility to simply notify the tab bar controller that a selection has been made. The tab bar controller can respond to that message by dismissing the popover. You can take advantage of the fact that UIViewController already has a method for accessing the tab bar controller that contains a given controller.
So it would look something like this:
#interface MyTabBarController : UITabBarController
- (void)itemWasSelected;
#end
#implementation MyTabBarController {
UIPopoverController *popover;
}
- (void)itemWasSelected {
[popover dismissPopoverControllerAnimated:YES];
}
#end
//////////////
#implementation TableController
- (void)tableView:(UITableView *)tv didSelectRowAtIndexPath:(NSIndexPath *)path {
// Do whatever else you want to do
MyTabBarController *tabController = (MyTabBarController *)self.tabBarController;
[tabController itemWasSelected];
}
With this solution, the table controller doesn't have to know anything about the popover; it just has to know that it's going to be presented inside a MyTabBarController, which seems a reasonable thing for it to know.
You could create a singleton class to track your popover status and then make it available to all classes equally and easily. That way it could easily be updated and accessed from any code without having to go straight to overburdening the app delegate even though thats basically the same idea but a bit cleaner in its own singleton.

In objective C, how do I programmatically access a UIViewController?

Let's say I have a tabbar application that nests a lot of uitableviewcontrollers and uiviewcontrollers. What is the line of code that i would use to get a handle on a particular controller?
For example, let's say I want to grab the uitableviewcontroller which is currently the 3rd tab of the tab bar controller. I am in the MyObject.h/m file which has the following code:
MyObject.h
#import <Foundation/Foundation.h>
#import "MyAppDelegate.h"
#interface MyObject : NSObject
-(void) myfunction;
#end
MyObject.m
#import "MyObject.h"
#implementation MyObject
-(void) myfunction {
UITableViewController * mytvc = /*Some line of code*/;
}
#end
What do i replace /*Some line of code*/ with such that mytvc is a variable that points to the uitableviewcontroller i'm interested in?
I am using XCode 4.2. I have a storyboard that illustrates each of my uiviewcontrollers. The Tabbar view controller was the first thing xcode made for me. So I think that means it's the top of the app?
If it is your root view controller, and you have access to your app delegate, you should be able to access your controller like this:
appDelegate.window.rootViewController
rootViewController.tabBarController.viewControllers is an array of the root view controller for each tab. You can set the active view controller by setting tabBarController.selectedViewController or tabBarController.selectedIndex.
From the app delegate it's self.window.rootViewController.tabBarController. From a view controller it's self.view.window.rootViewController.tabBarController.
[[self.view.window.rootViewController.tabBarController.viewControllers objectAtIndex:2] myFunction] would access your function from another view controller. That is assuming your third tab is not a navigation controller.
If your tab has a navigation controller in it, then you have to add the navigation controller syntax on top of it. [[[self.view.window.rootViewController.tabBarController.viewControllers objectAtIndex:2] navigationController].visibleViewController myFunction] would be the full path. Looking at it now it seems a bit ridiculous.

Reference the main view controller... need the code

I have a view based app (not navigation or tab based...)
My main view controller is called from the app delegate and initiated from a xib.
Then I use presentModalViewController to bring another view on the screen with it's own xib and view controller.
I have no problems passing data to that view controller.
However, when I dismiss the second view controller, I want to send data back to the main view controller for my app, but I just can't figure out how to reference it. Actually, I'd like to call a method in the main view controller if possible.
I've been struggling with this a bit and have found suggestions online but I just can't seem to get it to work. I'm hoping someone can provide the sample code to do this.
P.s. is this "main view controller" still referred to as a "root view controller" or is that term only used when dealing with a view controller stack (i.e. navigation or tab view controller)
EDIT:
I'm sure Bryan's solution would work so I have accepted as answer. However I ended up using NSNotificationCenter to get this to work and I find it a bit simpler to understand as a beginner
You can use the delegation pattern. In your modal view controller's header file, create an interface for a new delegate protocol...
#protocol ModalViewControllerDelegate <NSObject>
- (void)sendData:(Data *)someData;
#end
...and give your ModalViewController a new instance variable that implements this protocol:
#property (nonatomic, assign) id<ModalViewControllerDelegate> delegate;
Your main view controller should implement this protocol...
#interface MainViewController : UIViewController <ModalViewControllerDelegate> {
...and set itself as the delegate before it presents the modal view controller:
ModalViewController *modalViewController = [[[ModalViewController alloc] init] autorelease];
[modalViewController setDelegate:self];
// Present modal view controller
The main view controller should implement the delegate protocol's method:
- (void)sendData:(Data *)someData {
NSLog("I have just received some data: %#", someData);
}
Then inside your modal view controller, you can simply call the following method whenever you want to send data back to the main view controller:
[delegate sendData:someData];

Problem reloading gridView data after dismissing a modal view controller

I am presenting a modal view controller to show detailed information. I have it set up so that any change to the information in the modal view controller will change the information in its parent view controller.
The information is changed, but I cannot manage to reload the gridView data when the modal view controller is dismissed. Right now I have the action to dismiss the modal inside the modal view controller. Everything works good, I just can't reload the data for the gridView from the modal view controller.
I read somewhere that one of the options is to create a delegate that will be able to dismiss the modal view controller from the parent view controller, I just can't seem to find examples or nice tutorials on how to go by doing this. The truth is that I know how to use the delegates, but not quite sure on how to properly implement one.
Can anyone please point me in the right direction here? Maybe someone has a better option. I am open to any suggestions.
If I understand clearly, you want to reload the grid contained in your parent view controller when you dismiss the modal view controller. If so, here how:
Declare a protocol in your ModalViewController by doing something like
#protocol MyViewControllerDelegate;
#interface MyViewController : UIViewController {
id<MyViewControllerDelegate>delegate;
// Your stuff
}
#property (nonatomic, assign) id<MyViewControllerDelegate>delegate;
#end
#protocol MyViewControllerDelegate <NSObject>
-(void)viewControllerWasDismissedOrAnyOtherNameYoudLike;
#end
And in your .m file, just #synthesize delegate.
Just when you call dismissModalViewController:animated:, also call [delegate viewControllerWasDismissedOrAnyOtherNameYoudLike].
In your view controller with the grid view, import the header file of your modalviewcontroller, conform to the protocol
#interface MyGridViewController : UIViewController <MyViewControllerDelegate>
When you init the Modalview controller, set the delegate to self, and implement the viewControllerWasDismissedOrAnyOtherNameYoudLike method.
VoilĂ !