UIMenuController and the responder chain: what's going on? - iphone

I am using the UIMenuController on a custom UIView subclass. This means it can become first responder, and claims it canPerformAction on the "delete" action.
I would also like this view's superview (also a custom UIView) to be able to use the menu controller, so on that superview, I have marked it as being able to be first responder, and implemented canPerformAction for different actions ("copy" and "cut" in this case).
Here's the thing-- when I make the menu visible from the (first) subview, it puts all three actions in the menu: delete, copy, and cut. In the debugger, I see canBecomeFirstResponder and canPerformAction being invoked on both views before the menu appears.
What's going on here? Why isn't the menu controller restricted to the view that's become first responder? Or am I not diagnosing this correctly?
Thanks.

What code are you using?
In the documentation for canPerformAction:withSender:,
This default implementation of this method returns YES if the responder class implements the requested action and calls the next responder if it does not. ... Note that if your class returns NO for a command, another responder further up the responder chain may still return YES, enabling the command.
It seems to be contradictory, saying the default implementation recurses up the responder chain, but then also that UIMenuController recurses up the responder chain if you return NO.
The easiest fudge is probably to override -nextResponder to return nil, but that might have other side-effects (for one, actions with a "nil" target go up the responder chain by default!).

Related

Is it possible to insert NSObjectController to responder chain?

I've created an instance of NSObjectController (MenuObject on the image below) and method(test5:) for item's action there.
NSMenuItem is gray when I create a connection to First responder.
And it works fine when I create a IBAction directly.
I think it's because my NSObjectController(MenuObject) doesn't part of responder chain. No one can responds to selector and that's why item is grey. But how to fix it?
Thanks.
NSObjectController is a data-flow controller. Putting it in the responder chain does not make real sense.
However, you should read about the responder-chain for action messages. Doing so, you will prefer to put the action method into a window controller.

Swift: Deselect UITextField

is it possible to deselect a UITextfield programmatically?
I have several textfields with a pickerView as input instead of the keyboard. But when I click on a cancel button I want to deselect the current textfield.
func resignFirstResponder() -> Bool
textField.resignFirstResponder()
Notifies the receiver that it has been asked to relinquish its status
as first responder in its window.
Discussion The default implementation returns YES, resigning first
responder status. Subclasses can override this method to update state
or perform some action such as unhighlighting the selection, or to
return NO, refusing to relinquish first responder status. If you
override this method, you must call super (the superclass
implementation) at some point in your code.
Availability Available in iOS 2.0 and later.
func becomeFirstResponder() -> Bool
textField.becomeFirstResponder()
Notifies the receiver that it is about to become first responder in
its window.
Return Value YES if the receiver accepts first-responder status or NO
if it refuses this status. The default implementation returns YES,
accepting first responder status.
Discussion Subclasses can override this method to update state or
perform some action such as highlighting the selection.
A responder object only becomes the first responder if the current
responder can resign first-responder status (canResignFirstResponder)
and the new responder can become first responder.
You may call this method to make a responder object such as a view the
first responder. However, you should only call it on that view if it
is part of a view hierarchy. If the view’s window property holds a
UIWindow object, it has been installed in a view hierarchy; if it
returns nil, the view is detached from any hierarchy.
Availability Available in iOS 2.0 and later.

Full-screen UIView subclass as veil not consuming touch events

I have a tab bar app that works. Each tab is a UINavigationController whose root view is some kind of UIViewController, often a UITableViewController.
There are instances in the app where I want to display a full-screen "veil" with a message about what's happening until some operation completes. The point is to swallow up any touches on the UI that would navigate away from where the operation started.
The veil is a UIView subclass. There is one singleton instance of the class. When displayed, I insert it as a subview of the UITabBarController view. The view appears over the entire UI, tab bar included. Great!
Here's the problem. I can tap the tabs and the UI changes. What I would have expected is that my veil view would have just swallowed up the touches.
I have implemented in my veil class the various touches{Began|Ended|Moved|Canceled} methods (as do-nothing methods), but the touches are still picked up by the tab bar, and frankly by any object under whereever I happen to touch.
I've also tried overriding a number of other methods including nextResponder, hitTest:withEvent:, etc, to no avail.
I am a little stumped at this point. I'm hoping someone will have some sage advise. :-)
Thanks.
It's not safe to modify the view hierarchy of framework classes. You would be much better-served simply adding it as a subview of the window itself. As for consuming touches, if making this change doesn't work, then you should also verify that userInteractionEnabled is set to YES on the view. You should not have to actually implement any touch-related methods.
I also had this problem and came up with a hacky solution. In the init of your custom UIView class, create a dummy UIView that's impossible to hit, for example [[UIView alloc] initWithFrame:CGRectMake(-1, -1, 0, 0)]. Or actually, I think any UIView not attached to the window works. Then, in hitTest:withEvent:, have it return the dummy view for every point not in your area of interest.

How to restore previous firstResponder?

I'm having some difficulty understanding exactly how the responder chain works in an iPhone application.
My situation is as follows. I have two UIViewControllers that are installed on a tab bar controller. Call them view controller A and B. They are unrelated in the sense that neither of them has a reference or knows about the other.
Both A and B need to respond to remote control events (the play/pause/stop buttons). Controller A wants to respond to these events all the time, whereas B only wants to respond to them when the user selects a certain function. When my app starts, A becomes the first responder immediately and is able to receive the remote control events. When B wants to receive the events, it becomes first responder, and then begins to get the events instead of A.
The problem occurs when B is done using the remote control. At this point, B calls resignFirstResponder on itself. Thereafter, neither A nor B gets any remote control events.
I assumed that when B resigned first responder status, the thing that previously was the first responder would be restored. Is this not how it works?
If not, how do I restore A to first responder? Remember that A and B are separate tabs, so B doesn't have a reference to A or know that A is supposed to be the first responder. So I don't want to explicitly call becomeFirstResponder on A. What I need instead is a way to get the previous first responder and restore it (I think). I'm a bit puzzled as to why this doesn't just happen automatically.
Thanks,
Frank
The docs on UIResponder as well as the Event handling guide for iPhone OS indicate that the responder chain isn't exactly laid out how you expect it to be. It's not a linked chain of potential responders which can be tacked to and pulled from. Rather, it is directly associated with the view hierarchy currently presented to the user.
What this means is that when B resigns first responder, B's view controller (if there is one) or its superview becomes first responder, presuming is has implemented canBecomeFirstResponder:. If it can't, its view controller or superview becomes first responder, all the way up to the UIApplication.
If A and B are not in a hierarchy where one is a subview of the other, the responder chain will not return first responder to A when B resigns. Instead, B will resign first responder to its superview. What you most likely want to do is to implement becomeFirstResponder: in the view that is above both A and B. The method would simply hand first responder over to A. That way, B can grab first responder, and later, when it resigns it, the superview will hand it back to A.

UINavigationController intercepting – popViewControllerAnimated:

So the the problem is that when someone touches the back button on the UINavigationControler, I would like to run some code to update the datasource.
The problem is that i cant seem to find the right delegate to do it. only these are available on the nav controller delegate, and i want the 'didfinishshowing' type method.
– navigationController:willShowViewController:animated: optional method
– navigationController:didShowViewController:animated: optional method
The next best place i thought was the nav bar but when i try that.
Terminating app due to uncaught
exception
'NSInternalInconsistencyException',
reason: 'Cannot manually set the
delegate on a UINavigationBar managed
by a controller
This makes sense retrospectively, as you don't want some hacker messing around with the internals of the nav controller and stopping it from working.
This must is a common problem, and i have missed something simple.
Just so we're clear: view A is the starting point. User taps something and you slide right to view B. User taps the back button and you're going from B back to A and you want to do something as a result of the 'back' action.
There are three ways to do it (and on neither do you have to go near the navigationController -- these apply to the underlying viewControllers themselves):
As dmercredi suggests override viewWillAppear on view controller A so when you're heading back to it, it refreshes itself. Problem is that viewWillAppear is also called when A is called the very first time. So you'll have to set some sort of flag to distinguish between the first viewWillAppear and any subsequent ones when returning from B.
Override viewWillDisappear on view controller B and do your refreshing there. This will only get called when B is about to go away. If there's something on B that goes one level deeper or brings up a modal dialog on top, viewWillDisappear is going to get called so again you'll have to distinguish between the coming and the going.
Decouple the various views and use the delegate pattern. View controller A sets itself as a delegate of B and when B updates something it invokes the delegate method, so A is notified of the change and can update whatever it needs to. You can invoke the delegate method any time the user makes a change inside B or override viewWillDisappear and just do it one time on the way out.
Add your refresh code to the viewWillAppear:(BOOL)animated method on the view controller that is about to be displayed. In your case, that is the view controller that's already on the navigation stack.