Catching UINavigationController's native back button action - iphone

There are many questions about overriding navigation controller's back button action, but everybody tells that there is no direct way to do this. I've implemented this by inheriting UINavigationController and overriding UINavigationBarDelegate method. But the problem is that interface of UINavigationController doesn't implement UINavigationBarDelegate protocol (but it implements this method, because this code works:), and I afraid that app will be rejected by Apple, considering that here is used undocumented API.
So, the question is: how do you think, is it private API usage or not? Here is the code:
#interface CustomNavigationController : UINavigationController <UINavigationBarDelegate>
#end
#implementation CustomNavigationController
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item {
BOOL shouldPop = [[self topViewController] enableBackButton]; // every view controller overrides this method to disable or enable pop of view controller
if (shouldPop) {
//here is the warning about "UINavigationController may not respond to selector"
return [super navigationBar:navigationBar shouldPopItem:item];
} else {
return NO;
}
}
#end

Apple's documentation on UINavigationController: This class is not intended for subclassing.
While this intention is not a ban and may not technically get you rejected (using doesn't appear to reference any private APIs) it should impact your design decision. You're likely to inherit unexpected behavior now or potentially in the future (if something changes to the internals of the superclass). Navigation controller's are handy things, but depending on what you need, you might just be better off rolling your own.
One alternative might be to employ a standard navigation controller, but set it's navigationBar property to hidden. You may then design and include your own navigation bar in a XIB with whatever buttons targeted at whatever actions you wish (e.g., a back button simply calling popViewController).
EDIT: Subclassing of UINavigationController is no longer discouraged.

Related

Accessing a instance method within another view controller

I have a tab bar controller with 4 tabs. Each tab has its own view controller and a UIWebView.
Let's say I have a button (button1) in vc1 and an instance method onClick1 as well. In vc2 I have a method named reload. My question is, how do I access the specific instance method, onClick1 in vc2, from vc1?
For further detail, I'm actually trying to code a simple shopping utility for the iPhone. When a user adds an item to the cart from the browse view, I want to be able to automatically reload the cart view.
Below are some examples of what I mean. This problem has been more difficult than I thought. I'm not sure if I have redesign my application or what. Perhaps have both vc1 and vc2 belong to a subclass of vcmain and have reference to each of them there? However, if I do that, then how do I refer them to their corresponding .xib? Thanks guys!
#implementation viewController 1
//Reloads vc2
-(IBAction) onClick1: (id) sender {
//Calls vc2 reload
[vc2 reload];
}
#end
#implementation viewController 2
//Reload View
-(void)reload {
[webView reload];
}
#end
You've basically got three approaches, each one of which has plusses and minuses. I'm going to give you a high-level overview and let you go to Apple's quite comprehensive documentation for details. Hopefully I can give you the right terms to google for more specific help.
The approach that #dredful quite ably details is to have a handle to the "other" view controller(s) and call methods on them directly. That works fine, but it can be confusing and cumbersome handing pointers to all your controllers around, and traversing the view hierarchy to get get at the controller you want can be very tricky indeed.
The second approach is Key-Value Observing. You can register one view controller to "watch" a particular key (named property) of another view controller, and fire a particular selector when various things happen with it. This is kind of magical and nice, although at some point you have to have pointers to both controllers at the same time, which doesn't entirely relieve the downside of the "call it directly" method above. It is also a sort of unfortunate coupling of view control and data, kind of breaks MVC.
The third approach is using NSNotificationCenter. A class can post a notification, and any other object that registers itself to listen for that sort of notification can be triggered when that happens. It's nice because you might have LOTS of different objects adding items to the cart, and they can just shoot the notification center a note (even passing it an object or arbitrary data, if it wants), and the cart view can consume those notifications, catch the passed objects, and do its thing, not caring in particular who's talking to it. It keeps separate pieces of your application nicely decoupled. The downside is, it's got a bit of overhead to it, and whatever selector the notification-consuming class performs happens synchronously, so you can't hide network activity or some other long process there.
I'm thinking you should already have something like a base UIViewController (let's call it MyTabBars) that has a UITabBarController *tabBarController containing all your Tab Bar View Controllers. If that sounds familiar, you will want a method in MyTabBars called -(void)reloadCart. reloadCart will walk the array of tabBarController.viewControllers. On each viewController you can perform a respondsToSelector:#selector(reload) and if the specific viewController qualifies then it calls that selector method.
In order to do this you probably want all your vc1, vc2, ... files to have an id delegate defined and synthesized. When MyTabBars creates the different tab bars it sets the vc1 and vc2 delegate to self.
#implementation MyTabBars
//Reload Cart View
-(void)reloadCart {
for (UIViewController *thisUIViewController in tabBarController.viewControllers){
if ([thisUIViewController respondsToSelector:#selector(reload)]) {
[thisUIViewController reload];
}
}
}
#end
Assuming you know how to pass a delegate of MyTabBars into your vc1 and vc2, then you can now have the following code in vc1:
#implementation viewController1
//Reloads vc2
-(IBAction) onClick1: (id) sender {
//Calls MyTabBars reloadCart which will look for all tab bar view controllers
//that have the 'reload' method
[delegate reloadCart];
}
#end
This solution idea will cause MyTabBars to trigger any reload method found in any of our tab bar view controllers. Therefore be careful with the naming of such a method in your vc1, vc2, etc files. This solution will trigger a unique vc method or multiple vcs with the same method depending on your naming convention.
Hope this helps.

iPhone: Switching Views From Outside Root Controller

I am using a UINavigationController to switch between views. What I would like is for each view to have the ability to control when it is swapped out for another view by having buttons within the view. All of the samples I've seen thus far have placed buttons on a toolbar, which is located on the root view containing the Switch View Controller rather than the views, them self. Is it possible to do what I want? I can't figure how to wire up the connection back to the UINavigationController.
I'm having a difficult time wording this, so please feel free to let me know if you need additional clarification.
Read about delegates. Delegates are a common method to signal stuff from objects to their "parents" or any other objects.
You should have a "delegate" property (can really be called anything, this is just a convention) on your child views. You can have buttons in your child views.
You declare the delegate like this:
interface ChildView : UIViewController {
id delegate;
}
#property (assign) id delegate;
implementation ChildView
#synthesize delegate;
Then, when you set up your child views inside your UINavigationController, you do:
ChildView *childView = [[ChildView alloc] init...]
childView.delegate = self;
Inside your child view, you have a button method:
- (IBAction) didPressButton:(id)sender {
[self.delegate didPressButtonToSwapView];
}
Inside your UINavigationController, you have a method:
- (void) didPressButtonToSwapView {
[self popViewController]; // use the right names, I made these up :)
[self pushAnotherViewController];
}
You should also read about protocols which would make the above code more robust and would help you make sure you only call the right methods on delegate, but I did not want to complicate this example.
EDIT: yes, the cleanest way to get rid of the warning is to use a protocol. Just put this in a separate .h file:
#protocol SwitchingDelegate
- (void) didPressButtonToSwapView;
#end
Include this .h in the UINavController header, and say the UINavController implements the protocol:
#interface MyNav: UINavController <SwitchingDelegate> { ...
Implement the method in the implementation (you don't need anything more in the interface).
In your ChildView, say that the delegate must implement the protocol: change all the declarations to:
id<SwitchingDelegate> delegate;
The compiler then helps you by checking whether the delegate objects really implement the protocol. You should not get any warnings when you have completed all of this correctly.

viewWillAppear not called in UITableViewController?

I have a couple of UITableViewController classes and I just noticed that these methods aren't being called:
-(void)viewWillAppear:(BOOL)animated;
-(void)viewDidAppear:(BOOL)animated;
I read in http://discussions.apple.com/thread.jspa?threadID=1529769&tstart=0 that I would have to call those methods myself when pushing view controllers, but that's strange, since it works for anything but UITableViewController.
Also makes it a bit of an issue when I need to have a UITableViewCell deselected in the UIViewController that pushed the UITableViewController.
I can't find it in the documentation, but I think this might be because you are using a UINavigationController.
How about setting the UINavigationController's delegate property and then implementing UINavigationControllerDelegate? It provides two optional methods:
– navigationController:willShowViewController:animated:
– navigationController:didShowViewController:animated:
For example, navigationController:willShowViewController:animated: might look something like this:
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
if ([viewController isKindOfClass:[UITableViewController class]]) {
[viewController viewWillAppear:animated];
}
}
Anyway, this will get you the behavior you want without having to hack calls to viewWillAppear: all over your project.
Did anyone resolve this because the original post is correct - simply using UITableViewController and pushing the table view of that controller onto the navController does NOT trigger these methods despite the fact that it should. I have a series of UITableViewControllers and table views that are pushed and popped to display hierarchical data - nothing fancy, but the "viewWill/Did/Appear/Disappear" methods are never called. Only the viewDidLoad and viewDidUnload are called.
There must be a wiring problem in both our setups, but simply pushing a view into the navigationController should be all that is required (?) - hard to believe that this could have gone unnoticed as a fundamental bug this long.
?
These two methods are called by default to notify for the changes. UITableViewController is a subclass of UIViewController, so there will be the same behavior. You can see more in the View Controller Programming Guide
The viewWillAppear: and viewDidAppear: methods give subclasses a chance to perform any additional actions related to the appearance of the view.
How do you know that these methods are not called? Can you provide some more codes, or at least you test them with a NSLog() to see if there are some messages printed.
I'm seeing the same problem. I have a simple UIView from the IB and I do a addSubview with a class that extends UITableViewController.
I can see the view of the TableViewController without problems in my application, but the viewWillAppear function is never called in this situation.
Well, the discussion linked from the question has the answer right in it. UINavigationController needs to receive the "viewWillAppear" message in order for it to send those messages to the view controllers you push onto it.
So ironically if you don't do what Apple recommends, and you subclass UINavController for your view controller, then everything works great.
However, if you just create a UINavController inside of your view controller, then you need to implement "viewWillAppear", "viewDidAppear" and so on and forward those to your nav controller.
Note that this is especially important if you're using Three20, because its view controller hierarchy expects the "viewWillAppear" message to be received. If its not you can end up with TTTableViews that don't draw.
The same can occur if you use a UITabViewController. You need to force the viewWillAppear call by implementing either the UITabViewControllerDelegate or UINavigationControllerDelegate callbacks
This explanation may help: http://www.mlsite.net/blog/?p=210

Delegates Vs. Notifications in iPhoneOS

I am trying to call a method in my root view controller from a child view controller such that when I change my options they will automatically update the root view, which will in turn update several other view controllers. For the second part I have used notifications, but for this first I am trying to use a delegate because it (so I have been lead to believe) is a good programming practice. I am having trouble making it work and know that I can set up another notification easily to do the job. Should I continue trying to implement the delegate or just use a notification?
Delegating is a good programming practice for many situations but that doesn't mean you have to use it if you're not comfortable with it. Both delegating and notifications help decouple the view controllers from each other, which is a good thing. Notifications might be a little easier to code and offer the advantage that multiple objects can observe one notification. With delegates, such a thing cannot be done without modifying the delegating object (and is unusual).
Some advantages of delegating:
The connection between delegating object and delegate is made clearer, especially if implementing the delegate is mandatory.
If more than one type of message has to be passed from delegatee to delegate, delegating can make this clearer by specifying one delegate method per message. For notifications, you can use multiple notification names but all notifications end up in the same method on the side of the observer (possibly requiring a nasty switch statement).
Only you can decide what pattern is more appropriate for you. In any case, you should consider not having your view controller send the notification or the delegate message. In many cases, the view controller should change the model and then the model should inform its observers or its delegate that it has been changed.
Implementing a delegate pattern is simple:
In your ChildViewController.h, declare the delegate protocol that the delegate must implement later:
#protocol ChildViewControllerDelegate <NSObject>
#optional
- (void)viewControllerDidChange:(ChildViewController *)controller;
#end
At the top of the file, create an instance variable to hold the pointer to the delegate in your ChildViewController:
#protocol ChildViewControllerDelegate;
#interface ChildViewController : UIViewController {
id <ChildViewControllerDelegate> delegate;
...
}
#property (assign) id <ChildViewControllerDelegate> delegate;
...
#end
In RootViewController.h, make your class conform to the delegate protocol:
#interface RootViewController : UIViewController <ChildViewControllerDelegate> {
...
In the RootViewController implementation, implement the delegate method. Also, when you create the ChildViewController instance, you have to assign the delegate.
#implement RootViewController
...
// in some method:
ChildViewController *controller = [[ChildViewController alloc] initWithNibName:...
controller.delegate = self;
...
- (void)viewControllerDidChange:(ChildViewController *)controller {
NSLog(#"Delegate method was called.");
}
...
In the ChildViewController implementation, call the delegate method at the appropriate time:
#implementation ChildViewController
...
// in some method:
if ([self.delegate respondsToSelector:#selector(viewControllerDidChange:)]) {
[self.delegate viewControllerDidChange:self];
}
...
That's it. (Note: I have written this from memory so there are probably some typos/bugs in it.)
I would like to add:
objects receiving notifications can
react only after the event has
occurred. This is a significant
difference from delegation. The
delegate is given a chance to reject
or modify the operation proposed by
the delegating object. Observing
objects, on the other hand, cannot
directly affect an impending
operation.
Typically, if you need to update the UI based on a change to data in a model, you would have the view controllers observe the relevant model data and update their views when notified of changes.
I see delegation as a bit more formal and like the distinction that Peter Hosey shared recently:
The difference is that delegation is
for to-one (and bidirectional)
communication, whereas notifications
are for to-many, unidirectional
communication.
Also, I have found that (completely) updating the view in viewWillAppear: works fine (but this is not the best solution where performance is a concern).
Notifications can make the runtime behavior of your program significantly more complex. Think of it like a goto with multiple destinations. The order of those destinations is not defined. If you ever crash there is little stack trace information.
There are cases when it makes sense to use notifications--the typical one being to communicate a model change or a global state change to your views. Example, the network is down, the application will resign, etc!
It is worthwhile to learn the delegate pattern in iOS. Delegates give you complete stack traces when you debug. They result in significantly simpler runtime behavior while still achieving the goal of decoupling your objects.
Delegates are a little hard to get used to, but I think it's the best practice and, like Apple, they just work.
I always use the formal protocol declaration. It's a bit more logical in my mind, and it's very clear in the code. I suggest using a UIView to change your options instead of a controller. I always use one main controller and have a lot of subclassed UIViews that the one controller can control. (However, you can modify the following code for a controller, if you really need a controller instead of a normal view.) In the header file of the child view, make it look like this:
// ChildView.h
#import <UIKit/UIKit.h>
#protocol ChildViewDelegate; // tells the compiler that there will be a protocol definition later
#interface ChildViewController : UIView {
id <ChildViewDelegate> delegate;
// more stuff
}
// properties and class/instance methods
#end
#protocol ChildViewDelegate // this is the formal definition
- (void)childView:(ChildView *)c willDismissWithButtonIndex:(NSInteger)i; // change the part after (ChildView *)c to reflect the chosen options
#end
The method between #protocol and the second #end can be called somewhere in the implementation of the ChildView, and then your root view controller can be the delegate that receives the 'notification.'
The .m file should be like this:
// ChildView.m
#import "ChildView.h"
#implementation ChildView
- (id)initWithDelegate:(id<ChildViewDelegate>)del { // make this whatever you want
if (self = [super initWithFrame:CGRect(0, 0, 50, 50)]) { // if frame is a parameter for the init method, you can make that here, your choice
delegate = del; // this defines what class listens to the 'notification'
}
return self;
}
// other methods
// example: a method that will remove the subview
- (void)dismiss {
// tell the delegate (listener) that you're about to dismiss this view
[delegate childView:self willDismissWithButtonIndex:3];
[self removeFromSuperView];
}
#end
Then the root view controller's .h file would include the following code:
// RootViewController.h
#import "ChildView.h"
#interface RootViewController : UIViewController <ChildViewDelegate> {
// stuff
}
// stuff
#end
And the implementation file will implement the method defined in the protocol in ChildView.h, because it will run when the ChildView calls for it to be run. In that method, put the stuff that happens when you'd get the notification.
In this case, you don't need to use either delegation or notification because you don't really need to communicate directly between your views. As gerry3 said, you need to change the data model itself and then let all other views respond to that change.
Your data model should be an independent object that all your view controllers have access to . (The lazy way is to park it as an attribute of the app delegate.) When the user makes a change in View A, View A's controller writes that change to the data model. Then whenever Views B through Z open up, their controllers read the data model and configure the views appropriately.
This way, the neither the views, nor their controllers need to be aware of each other and all changes occur in one central object so they are easily tracked.
Is it really necessary for your root view controller to know about the changes, or just the subviews?
If the root controller does not have to know, having the settings send out the notifications the other views are looking for seems like a better answer to me, as it simplifies code. There is no need to introduce more complexity than you have to.

how does one make a subview firstResponder

i am trying to get a subview to become firstResponder. my understanding is that this is done in its viewDidAppear method, like so:
- (void)viewDidAppear {
[self becomeFirstResponder];
}
while overriding canBecomeFirstResponder to return YES:
- (BOOL)canBecomeFirstResponder {
return YES;
}
however, when i insert the subview in its parent view's viewDidLoad method:
- (void)viewDidLoad {
subViewController = [[SubViewController alloc] init];
[self.view insertSubview: subViewController.view atIndex: 0];
[subViewController viewDidAppear: NO];
[super viewDidLoad];
}
(i call viewDidAppear manually, because it does not get triggered automatically), the subview does not become firstResponder.
why does the subview not become firstResponder? and how can i make it firstResponder?
thanks,
mbotta
btw, this is a rewrite of my original question:
i am trying to build an iphone app where a rootviewcontroller object manages two subviews, one of which should react to a user shaking his iphone.
after some digging, i concluded the subview must be made firstResponder in its view controller's viewDidAppear method. moreover, the canBecomeFirstResponder method should be modified to return YES.
so here's what happens: i instantiate the rootviewcontroller in the app delegate. in its viewDidLoad method, i tell it to addSubView firstViewController. all of this works just beautifully.
however, firstViewController does not react to any shaking. i sprinkled some NSLogs around and found that while we DO tell firstViewController in canBecomeFirstResponder to return YES, and while we DO tell it to [self becomeFirstResponder] in viewDidAppear, in actual fact, it is not the firstResponder.
so, my questions are:
1) does a subview actually need to be firstResponder in order to react to shaking?
a) if not, how does one make a subview react without being firstResponder?
2) how does one make a subview firstResponder?
what makes this interesting is that if i perform the same sequence (canBecomeFirstResponder, [self firstResponder], motionBegan:) in a different project with only one view controller, it all works flawlessly. clearly, i must be hitting a design flaw of my own making.
thanks,
mbotta
Not 100% sure, but this could be your problem. If we could see the offending methods it might be easier.
From the Event Handling Best Practices (emphasis added by me):
If you handle events in a subclass of
UIView, UIViewController, or (in rare
cases) UIResponder,
You should implement all of the event-handling methods (even if it is
a null implementation).
Do not call the superclass implementation of the methods.
If you call the superclass methods the events are probably getting passed along to the nextResponder.
EDIT
The Event Handling Best Practices link above is dead. I couldn't find that pull quote anywhere, but Event Handling Guide for UIKit Apps seems to be the most relevant.