Make a firstResponder UIView by containing another firstResponder (a UITextView) - uikit

I have a UIView subclass (MyView) that contains a UITextView. I want MyView to use UITextView for all UIResponder methods like so:
#implementation MyView
- (BOOL)canBecomeFirstResponder {
return _textView.canBecomeFirstResponder
}
- (BOOL)becomeFirstResponder {
return [_textView becomeFirstResponder];
}
- (BOOL)canResignFirstResponder {
return [_textView canResignFirstResponder];
}
- (BOOL)resignFirstResponder {
// UIResponder documentation says [super resignFirstResponder]
// must be called somewhere in this method
BOOL superResignedFirstResponder = [super resignFirstResponder];
if (superResignedFirstResponder) {
return [_textView resignFirstResponder];
} else {
return NO;
}
}
- (BOOL)isFirstResponder {
return [_textView isFirstResponder];
}
#end
However, as I'm reading through Apple's Event Delivery: The Responder Chain documentation, I think this may be an incorrect implementation. I can't find any documentation or posts about how to create a UIResponder with another UIResponder.
UIKit has a notion of exactly 1 firstResponder, so when MyView handles -becomeFirstResponder and returns YES, it seems reasonable for UIKit to think MyView is the firstResponder. However, since I in turn call -[UITextView becomeFirstResponder] within -[MyView becomeFirstResponder], one of the two must win and one must lose. Which wins and which loses? If UITextView is the firstResponder, then why should -[MyView isFirstResponder] ever return YES?
Does anyone have any advice? Is my above implementation correct?

Though I found other evidence that people solved this problem the same way. This implementation is causing me problems. TLDR: I think you're just not supposed to compose UIResponder objects.
My bug:
A consumer calls a method on MyView, and MyView programmatically calls -[UITextView becomeFirstResponder]. No one ever taps on MyView's internal UITextView.
A consumer wants to dismiss the keyboard. We can verify that UITextView is the firstResponder because the private API -[UIApplication.sharedApplication.keyWindow firstResponder] returns UITextView.
A consumer calls [UIApplication sendAction:#selector(resignFirstResponder) to:nil from:nil forEvent:nil], but this call returns NO. While this call is made, UIKit doesn't call -[UITextView canPerformAction:withSender:] or -[UITextView targetForAction:withSender:].
However, if instead:
the user taps on MyView's UITextView
A consumer wants to dismiss the keyboard. We can verify that UITextView is the firstResponder because the private API -[UIApplication.sharedApplication.keyWindow firstResponder] returns UITextView.
A consumer calls [UIApplication sendAction:#selector(resignFirstResponder) to:nil from:nil forEvent:nil], and now this call returns YES. While this call is made, UIKit calls -[UITextView canPerformAction:withSender:] and -[UITextView targetForAction:withSender:] as expected, and then of course calls -[UITextView resignFirstResponder], which succeeds.
I have no idea why in the first case [UIApplication sendAction:#selector(resignFirstResponder) to:nil from:nil forEvent:nil] doesn't delegate to UITextView properly, but I have to assume that since -[MyView becomeFirstResponder] without delegating to [super becomeFirstResponder] as the docs say, something got messed up. I think you're just not supposed to compose UIResponder objects.
--EDIT--
I still don't know for sure what's wrong, but I discovered that I have multiple UIWindows in my app, and I've heard from People That Know™ that multi-windowed apps can have occasional firstResponder issues.

Related

Understanding NSPopover with ARC

I'm somewhat puzzled by object lifetimes under ARC. Here’s a scenario which I think is probably common.
1) In response to some event, was load an NSViewController from a nib.
- (IBAction) doIt: (id) sender
{
InfoController *editor=[[InfoController alloc]initWithNibName:#"InfoController" bundle:nil];
[editor show: .... ]
}
2) The InfoController then displays an NSPopover.
3) Sometime later, the user clicks outside the NSPopover. The popover closes itself.
But when does the InfoController get released? For that matter, what's keeping it alive after doIt returns? In my implementation, InfoController is a data source and delegate for controls in its NSPopover, but in general data sources and delegates aren't retained, right?
I realize your question is a bit old now, but I came across it while researching a retain cycle with my NSViewController and NSPopover:
The NSPopover contentViewController property is retaining your NSViewController. That is why you can show the popover like you (and I) do as a response to an action, without another object retaining it. What I found though, is that to properly release the NSViewController under ARC, the contentViewController should be set to nil when the popover is closed. This is in my NSViewController subclass:
- (void)popoverDidClose:(NSNotification *)notification
{
self.popover.contentViewController = nil;
}

Activity Indicator above Button prevents Click Recognition

I have an UIButton "bn" and an UIActivityIndicator "ai" which is above the button (ai.center = bn.center).
As long as ai is visible and animating, I can't press the Button underneath ai's frame but out of the range I can.
Do I have to add GestureRecognition to ai or is there a smarter way to click on the "ai".
Kind regards. $h#rky
Can you simply set ai.userInteractionEnabled = NO;? I'm surprised it is enabled anyway, on an activity indicator - is this a standard component or have you made a subclass?
As an aside, it is usually poor UI design to have an interactive element that is covered by another view, particularly one which is used to indicate that something is "busy", but in your example of a clickable thumbnail image, it seems to make sense.
You need to override the UIView method
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
in the parent view. If the hit is within the button the you return the button, else you return nil. This is part of the the discussion on this method:
This method traverses the view hierarchy by sending the
pointInside:withEvent: message to each subview to determine which
subview should receive a touch event. If pointInside:withEvent:
returns YES, then the subview’s hierarchy is traversed; otherwise, its
branch of the view hierarchy is ignored. You rarely need to call this
method yourself, but you might override it to hide touch events from
subviews.
EDIT:
Say you have a UIView subclass which contains bn and ai, you can implement the method like this
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
if (CGRectContainsPoint(bn.frame, point)) {
return bn;
}
return nil;
}
that way your button will get the touch events (if they are within its frame) no matter if something is on top of it or not. You do not need to do anything else.
Use the following stuff:
First, create subclass for UIActivityIndicator with the following method override:
#interface MyActivityIndicator: UIActivityIndicator
#end
#implementation MyActivityIndicator
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
return NO;
}
#end
After that, use MyActivityIndicator everywhere in your project instead of UIActivityIndicator (in NIB file or in your code, depends where do you create it)

How does an NSView subclass communicate with the controller?

I am brand spanking new to Cocoa programming, and am still kind of confused about how things wire together.
I need a pretty simple application that will fire off a single command (let's call it DoStuff) whenever any point on the window is clicked. After a bit of research it looks like subclassing NSView is the right way to go. My ClickerView.m file has this:
- (void)mouseDown:(NSEvent *)theEvent {
NSLog(#"mouse down");
}
And I have added the View to the Window and have it stretching across the whole thing, and is properly writing to the log every time the window is clicked.
I also have my doStuff method on my controller (this could be refactored to its own class I suppose, but for now it works):
- (IBAction)doStuff:(id)sender {
// do stuff here
}
So, how do I get mouseDown in ClickerView to be able to call DoStuff in the controller? I have a strong .NET background and with that, I'd just have a custom event in the ClickerView that the Controller would consume; I just don't know how to do that in Cocoa.
edit based on Joshua Nozzi's advice
I added an IBOutlet to my View (and changed it to subclass NSControl):
#interface ClickerView : NSControl {
IBOutlet BoothController *controller;
}
#end
I wired my controller to it by clicking and dragging from the controller item in the Outlets panel on the View to the controller. My mouseDown method now looks like:
- (void)mouseDown:(NSEvent *)theEvent {
NSLog(#"mouse down");
[controller start:self];
}
But the controller isn't instantiated, the debugger lists it as 0x0, and the message isn't sent.
You could either add it as an IBOutlet like Joshua said, or you could use the delegate pattern.
You would create a Protocol that describes your delegate's methods like
#protocol MyViewDelegate
- (void)doStuff:(NSEvent *)event;
#end
then you'd make your view controller conform to the MyViewDelegate protocol
#interface MyViewController: NSViewController <MyViewDelegate> {
// your other ivars etc would go here
}
#end
Then you need to provide the implementation of the doStuff: in the implementation of MyViewController:
- (void)doStuff:(NSEvent *)event
{
NSLog(#"Do stuff delegate was called");
}
then in your view you'd add a weak property for the delegate. The delegate should be weak, so that a retain loop doesn't form.
#interface MyView: NSView
#property (readwrite, weak) id<MyViewDelegate> delegate;
#end
and then in your view you'd have something like this
- (void)mouseDown:(NSEvent *)event
{
// Do whatever you need to do
// Check that the delegate has been set, and this it implements the doStuff: message
if (delegate && [delegate respondsToSelector:#selector(doStuff:)]) {
[delegate doStuff:event];
}
}
and finally :) whenever your view controller creates the view, you need to set the delegate
...
MyView *view = [viewController view];
[view setDelegate:viewController];
...
Now whenever your view is clicked, the delegate in your view controller should be called.
First, your view needs a reference to the controller. This can be a simple iVar set at runtime or an outlet (designated by IBOutlet) connected at design time.
Second, NSControl is a subclass of NSView, which provides the target/action mechanism machinery for free. Use that for target/action style controls. This provides a simple way of setting the reference to your controller (the target) and the method to call when fired (the action). Even if you don't use a cell, you can still use target/action easily (NSControl usually just forwards this stuff along to its instance of an NSCell subclass but doesn't have to).
you can also use a selector calling method,
define two properties in custom class:
#property id parent;
#property SEL selector;
set them in view controller:
graph.selector=#selector(onCalcRate:);
graph.parent=self;
and call as:
-(void)mouseDown:(NSEvent *)theEvent {
[super mouseDown:theEvent];
[_parent performSelector:_selector withObject:self];
}

NSMenuItem custom view does not respond to keyEquivalent

I've set a custom NSView to a NSMenuItem to do my own drawing and rendering. However the 'keyEquivalent' assigned to the NSMenuItem does not seem to respond. I understand drawing and action handling needs to be self-handled but I can't seem to be able to capture keyEquivalent request no matter what I do. I've tried subclassing NSApplication's sendEvent but that doesn't work since my app is a NSStatusBarItem (LSUIElement) and the events from the NSEventTrackingRunLoopMode (when menu is down) do not reach NSApplication's sendEvent.
Then I've tried using:
- (BOOL)menuHasKeyEquivalent:(NSMenu *)menu forEvent:(NSEvent *)event target:(id *)target action:(SEL *)action
This doesn't work either as this is never called even though I've set the main menu's delegate to the controller.
Does anyone have any idea on how to capture 'keyEquivalent' events on NSMenuItems when using a custom view?
Thanks!
I know this is an old post and you are probably long past this, but I was having the same problem and encountered your post multiple times when trying to find a solution, so I thought I would share what worked for me.
I was able to work around the problem by subclassing NSApplication and overriding sendEvent. You mentioned you tried this with no success, so I don't know what the difference is, but I had no problem intercepting the events from a NSMenu in a NSStatusBarItem. My key equivalents are all ints, so I validate the keys, find the appropriate item, and then explicitly invoke the NSMenuItem action.
-(void)sendEvent:(NSEvent *)theEvent
{
if([theEvent type] == NSKeyUp){
NSInteger mod = ([theEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask);
if(mod == NSCommandKeyMask) {
NSInteger keyEquiv = [[theEvent characters] isEqualToString:#"0"]
? 10
: [[theEvent characters] integerValue];
if(keyEquiv > 0) {
NSMenuItem *item = [[(MyAppDelegate *)[self delegate] myStatusMenu] itemAtIndex:(keyEquiv - 1)];
if([[item keyEquivalent] integerValue] == keyEquiv){
[[item target] performSelector:[item action] withObject:item];
}
}
}
}
[super sendEvent:theEvent];
}

Getting around IBActions limited scope

I have an NSCollectionView and the view is an NSBox with a label and an NSButton. I want a double click or a click of the NSButton to tell the controller to perform an action with the represented object of the NSCollectionViewItem. The Item View is has been subclassed, the code is as follows:
#import <Cocoa/Cocoa.h>
#import "WizardItem.h"
#interface WizardItemView : NSBox {
id delegate;
IBOutlet NSCollectionViewItem * viewItem;
WizardItem * wizardItem;
}
#property(readwrite,retain) WizardItem * wizardItem;
#property(readwrite,retain) id delegate;
-(IBAction)start:(id)sender;
#end
#import "WizardItemView.h"
#implementation WizardItemView
#synthesize wizardItem, delegate;
-(void)awakeFromNib {
[self bind:#"wizardItem" toObject:viewItem withKeyPath:#"representedObject" options:nil];
}
-(void)mouseDown:(NSEvent *)event {
[super mouseDown:event];
if([event clickCount] > 1) {
[delegate performAction:[wizardItem action]];
}
}
-(IBAction)start:(id)sender {
[delegate performAction:[wizardItem action]];
}
#end
The problem I've run into is that as an IBAction, the only things in the scope of -start are the things that have been bound in IB, so delegate and viewItem. This means that I cannot get at the represented object to send it to the delegate.
Is there a way around this limited scope or a better way or getting hold of the represented object?
Thanks.
Firstly, you almost never need to subclass views.
Bind doesn't do what you think - you want addObserver:forKeyPath:options:context: (You should try to understand what -bind is for tho ).
When you say "the key seems to be it being the "prototype" view for an NSCollectionViewItem" I think you are really confused…
Forget IBOutlet & IBAction - they don't mean anything if you are not Interface Builder. "Prototype" means nothing in Objective-c.
The two methods in the view do not have different scope in any way - there is no difference between them at all. They are both methods, equivalent in every way apart from their names (and of course the code they contain).
If wizardItem is null in -start but has a value in -mouseDown this is wholly to do with the timing that they are called. You either have an object that is going away too soon or isn't yet created at a point you think it is.
Are you familiar with NSZombie? You will find it very useful.

Resources