I have a TextViewController which is a subclass of NSViewController which controls a View containing a NSTextView
I attach an instance of TextViewController to an existing view with the following code
TextViewController *textViewer;
...
[[self view] addSubview:[textViewer view]]; // embed new TextView in our host view
[[textViewer view] setFrame:[[self view] bounds]]; // resize the controller's view to the host size
textViewer.delegate = self;
[split.window makeFirstResponder:textViewer];
This works quite well, and the TextViewController traps the keyDown Event to perform various actions.
I wanted to allow users to select text in the NSTextView to copy to clipboard.
Unfortunately clicking in the NSTextView makes this the FirstResponder and stops TextViewController from responding to key presses.
I can force the FirstResponder back to my TextViewController, but this seems like a kludge.
- (void)textViewDidChangeSelection:(NSNotification *)aNotification {
[self.view.window makeFirstResponder:self];
}
I know I could subclass NSTextView to trap the keyDown Event, but this doesn't seem much better.
I am sure there must be a more elegant way of doing this.
I added a subclass of NSTextView which just passes the keyDown to the controller
#protocol MyTextViewDelegate
- (BOOL)keyPressedInTextView:(NSEvent *)theEvent;
#end
#interface MyTextView : NSTextView
#property (assign) IBOutlet NSObject <MyTextViewDelegate> *delegate;
#end
...
#implementation MyTextView
#synthesize delegate;
- (void)keyDown:(NSEvent *)theEvent {
if([self.delegate respondsToSelector:#selector(keyPressedInTextView:)]) {
if([self.delegate keyPressedInTextView:theEvent])
return;
}
[super keyDown:theEvent];
}
#end
Subclassing NSTextView sounds like the best solution for the problem you described. You want a text view that behaves like normal to select, copy, Cmd-A, etc. except that it also responds to special key presses you've defined. This is a standard use of a subclass. Trying to have the view controller handle things by playing games with the first responder will give you problems in various edge cases like the one you discovered.
Related
I'm trying to find a solution that allows me to get keydown events in a view controller.
I do not believe a view controller is part of the responder chain by default.
I would appreciate a sample of how to go about this. I have had trouble finding documentation I can understand on how to add the VC to the responder chain and get the events.
Thanks.
Miek
You can implement something like this:
-(void) globalKeyDown: (NSNotification *) notification
method in your controller class, and then just add the observer in awakeFromNib...or loadView method of your controller
- (void)awakeFromNib
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(globalKeyDown:)
name:#"my_keyEvent"
object:nil];
}
in your view class
-(void)keyDown:(NSEvent *)theEvent
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"my_keyEvent"
object:theEvent
userInfo:#{#"sender":self}];
}
NSViewController doesn't have a default way to do this. However, you can achieve this through subclassing NSView. Here is the basic idea:
If you create a view subclass, you can set your view controller as a delegate and create a delegate method that handles events.
You can declare a delegate protocol at the start of your view header.
Import your view header in the view controller header. Declare the view controller as implementing the protocol.
In your view keyDown send the event to the delegate.
Another way is to post NSNotifications in your keyDown and observe and handle the notifications in your view controller. Other ways also exist.
NSView Subclass with Delegate method explained
Here is the delegation example with an NSView subclass which declares a protocol in its header with one required method, an IBOutlet id property that conforms to the protocol. The NSView subclass calls this method to its delegate whenever it wants to. If the delegate is nil, that's fine in Cocoa. Also note, tangentially, I have added IB_Designable and IBInspectable to the view's color properties. This allows setting them in IB and requires the 10.10 SDK.
The app delegate has imported the NSView subclass in the AppDelegate.m implementation file and adopted the protocol in the AppDelegate class extension at the top of the .m file. In the #implementation section it also implements the method.
Also note in IB, I added an NSView to the window, then set its class to the custom NSView subclass in the inspector. Finally, I set its eventDelegate IBOutlet to the AppDelegate proxy in IB.
Custom NSView subclass interface
#import <Cocoa/Cocoa.h>
#protocol EventDelegatingViewDelegate <NSObject>
- (void)view:(NSView *)aView didHandleEvent:(NSEvent *)anEvent;
#end
IB_DESIGNABLE
#interface EventDelegatingView : NSView
#property IBOutlet id<EventDelegatingViewDelegate> eventDelegate;
#property IBInspectable NSColor *fillColor;
#property IBInspectable NSColor *strokeColor;
#end
Custom NSView subclass implementation
#import "EventDelegatingView.h"
#implementation EventDelegatingView
- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent {return YES;}
// The following two methods allow a view to accept key input events. (literally they say, YES, please send me those events if I'm the center of attention.)
- (BOOL)acceptsFirstResponder {return YES;}
- (BOOL)canBecomeKeyView {return YES;}
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
[self.fillColor set];
NSRectFill(self.bounds);
[self.strokeColor set];
NSFrameRect(self.bounds);
}
// Notice these don't do anything but call the eventDelegate. I could do whatever here, but I didn't.
// The NICE thing about delgation is, the originating object stays in control of it sends to its delegate.
// However, true to the meaning of the word 'delegate', once you pass something to the delegate, you have delegated some decision making power to that delegate object and no longer have any control (if you did, you might have a bad code smell in terms of the delegation design pattern.)
- (void)mouseDown:(NSEvent *)theEvent
{
[self.eventDelegate view:self didHandleEvent:theEvent];
}
- (void)keyDown:(NSEvent *)theEvent
{
[self.eventDelegate view:self didHandleEvent:theEvent];
}
#end
App Delegate (and eventDelegate!) implementation
#import "AppDelegate.h"
// Import the view class and if there were other files that implement any protocol
#import "EventDelegatingView.h"
// Declare protocol conformance (or more accurately, not only import that protocol interface, but say you're going to implement it so the compiler can nag you if you don't)
#interface AppDelegate ()<EventDelegatingViewDelegate>
#property (weak) IBOutlet NSWindow *window;
// For the simplest demo app we don't even need this property.
#property IBOutlet EventDelegatingView *eventDelegatingView;
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
}
// It's all right here. Receive a reference to a view and a reference to an event, then do as you like with them.
#pragma mark - EventDelegatingViewDelegate
- (void)view:(NSView *)aView didHandleEvent:(NSEvent *)anEvent
{
NSString *interestingEventNote;
switch (anEvent.type) {
case NSKeyDown:
case NSKeyUp:
{
// For simplicity we won't try to figure out the modifier keys here.
interestingEventNote = [NSString stringWithFormat:#"%# key was pressed.", anEvent.charactersIgnoringModifiers];
}
break;
case NSLeftMouseDown:
{
interestingEventNote = [NSString stringWithFormat:#"Left mouse down at point %# in window", NSStringFromPoint(anEvent.locationInWindow)];
}
break;
default:
break;
}
NSLog(#"%# %# aView=%#\n note=%#", self, NSStringFromSelector(_cmd), aView, interestingEventNote?interestingEventNote:#"Nothing worth noting");
}
#end
And that's it for the power of delegation. Basically it's callbacks of sorts and is a great way to build a class to enable it to defer something elsewhere as wanted. Moving some business logic to the right place in a fairly lazy and open and loosely coupled way.
NOTE: My code example shows using the app delegate. But the principal is the same. A view controller is little more than a delegate and you can add as much or as little as you like.
In your NSWidow (or NSWindowController) class implementation set your view controller as the first responder:
[self makeFirstResponder:yourViewControllerInstance];
You must, of course, make your NSViewController class return YES to the acceptsFirstResponder message.
So I am trying to make an application that has a button (doesn't have to be a button) that when you hover over it a pop-up window appears. I have been able to print a message to the log when i hover over the button, but I can't figure out how to set the Hidden property of the image to NO. I tried giving the NSButtonCell (the class that receives the hover event) a delegate, but calling
[myButtonCell setDelegate:delegateObject]
Doesn't give the object a delegate. If I could find a way for the buttonCell and image to communicate (they are both in the same xib) or get the buttonCell to call a function in one of the classes which has it as an instance it would be an easy task to figure out the rest.
My explanation is a bit diffuse so I will try to explain better: I have a window object, with a view object, which has a subclass of a NSButtonCell object (IBOutlet). In the subclass of the NSButtonCell (lets call it MyButtonCell) I have a method that when call needs to inform the view, or the window, that the method has been called.
I feel like I have looked everywhere, but can't find the solution. I thought I would make it with a delegate, but I can't set a delegate for the buttonCell so I am stuck…
Edit:
Here is the code for the NSButtonCell and the Delegate:
The delegate:
#interface MyView : NSView <MyButtonCellDelegate>
{
}
#property (assign) IBOutlet MyButtonCell *buttonCell1;
- (void)toggleField:(int)fieldID;
#end
#implementation MyView
- (void)toggleField:(int)fieldID
{
if (fieldID == 1) {
[self.field1 setHidden:!buttonCell1.active];
}
NSLog(#"toggling");
}
#end
MyButtonCell:
#protocol MyButtonCellDelegate
- (void)toggleField:(int)fieldID;
#end
#interface MyButtonCell : NSButtonCell
{
id <MyButtonCellDelegate> delegate;
}
#property BOOL active; //Used to lett the view know wether the mouse hovers over it
#property (nonatomic, assign) id <DuErButtonCellDelegate> delegate;
-(void)_updateMouseTracking; //mouse tracking function, if you know a better way to do it that would be lovely
#end
#implementation MyButtonCell
#synthesize delegate;
#synthesize active;
- (void)mouseEntered:(NSEvent *)event
{
active = YES;
[[self delegate] toggleField:1];
NSLog(#"entered");
}
- (void)mouseExited:(NSEvent *)event
{
active = NO;
[[self delegate] toggleField:1];
}
- (void)_updateMouseTracking {
[super _updateMouseTracking];
if ([self controlView] != nil && [[self controlView] respondsToSelector:#selector(_setMouseTrackingForCell:)]) {
[[self controlView] performSelector:#selector(_setMouseTrackingForCell:) withObject:self];
}
}
#end
Hope this is clear enough
I am not sure what you are really looking for, but if I understand what you are asking here:
I have a window object, with a view object, which has a subclass of a
NSButtonCell object (IBOutlet). In the subclass of the NSButtonCell
(lets call it MyButtonCell) I have a method that when call needs to
inform the view, or the window, that the method has been called.
correctly, one possibility is for your NSButtonCell to post a NSNotification to the default notification center and have your view or window or whoever needs to know be an observer for that notification. You are free to define your own custom notifications.
Another possibility would be for your subclass of NSButtonCell to use:
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait
and from your NSCell method that, when invoked, needs to inform it's view or window, can do:
[[self controlView] performSelectorOnMainThread:#selector( viewMethodToInvoke: ) withObject:anObject waitUntilDone:YES]
or
[[[self controlView] window] performSelectorOnMainThread:#selector( windowMethodToInvoke: ) withObject:anObject waitUntilDone:YES]
A third possibility is to do as you suggest and provide your NSButtonCell with an object that it can send a message to directly, but this is just the same thing as using performSelectorOnMainThread on the controlView or the controlView's window, but more work.
As for your mouse tracking code, I assume that you are using a NSTrackingArea. You can find documentation on them here: Using Tracking-Area Objects
I have a DetailViewController, which implementation file contains this code:
#import <UIKit/UIKit.h>
#interface DetailViewController : UIViewController <UITextFieldDelegate>
{
__weak IBOutlet UITextField *nameField;
__weak IBOutlet UITextField *numberField;
}
#end
In my storyboard, I have set the ViewController's to DetailViewController and connected the delegate of both of my UITextFields to my DetailViewController. The implementation file of my DetailViewController contains this method to dismiss the keyboard when tapping somewhere other than the text field:
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
[textField resignFirstResponder];
return YES;
}
This method is not called though, I have tested this using a breakpoint. What could be going wrong?
rdelmar is correct, the code you have only gets triggered when the user hits the "return" key on the keyboard, not when they click outside of the keyboard.
To get the behavior you are looking for, I'd add a Tap Gesture Recognizer to the view behind your text field, then put [nameField resignFirstResponder]; and [numberField resignFirstResponder]; in the tap gesture recognizer's code.
I created a sample project
added to the viewcontroller
added a textfield and connected its delegate
write the code in viewcontroller
(BOOL)textFieldShouldReturn:(UITextField *)textField
{
[textField resignFirstResponder];
return YES;
}
it worked fine.Tried weak it also worked both as instance variable and property
So check if that you are reallocating it anywhere and also check its memory is same there using breakpoint
Why do you use _weak? Remove it.
In your .m file do (in viewDidLoad):
[nameField setDelegate:self];
Same for the other textfield(s).
How to call a custom function when the user touches outside of the UITextField that has keyboard focus?
My goal is to call a function for textfield validation.
I tried this:
// Dismiss Keyboard
- (void)touchesEnded: (NSSet *)touches withEvent: (UIEvent *)event {
for (UIView* view in self.view.subviews) {
if ([view isKindOfClass:[UITextField class]])
{
[view resignFirstResponder];
[self validationForNoOfPassengers];
}
}
}
validationForNoOfPassengers is my method to check for validation. The problem here is that when I tap on other UI controls it does not call the above method. Above method is called only when touched outside on the screen (but not on UIcontrols). Any guidance?
Set your view controller (or some other object) to be the text field's delegate. Do the validation in -textFieldShouldEndEditing: or -textFieldDidEndEditing:.
EDIT
See my answer here for a better, general purpose solution that you can drop into any project.
ORIGINAL ANSWER
Other answers have suggested doing your validation in the text field delegate's textFieldShouldEndEditing: or textFieldDidEndEditing: methods. As you have discovered, when you touch a control, your text field doesn't send those messages to its delegate.
It doesn't send those messages because it is not ending editing. The text field remains first responder (the keyboard focus) when you touch another non-text control.
I don't know if you have just one or two text fields that have validation, or a lot of text fields that have validations. I don't know if you have just one or two non-text controls or a lot of non-text controls. The best solution for you really depends on what you have.
What you'd really like to do is check, on each new touch, whether there is a first responder, if if so, whether the new touch is outside the first responder. If so, send resignFirstResponder to the current first responder (which will make it send textFieldShouldEndEditing: to its delegate), and only proceed if resignFirstResponder returns YES.
If you only have one text field, you can make your top-level view be a subclass of UIView and override the hitTest:withEvent: method, like this:
MyView.h
#import <UIKit/UIKit.h>
#interface MyView : UIView
#property (weak, nonatomic) IBOutlet UITextField *textField;
#end
MyView.m
#import "MyView.h"
#implementation MyView
#synthesize textField;
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView *hitView = [super hitTest:point withEvent:event];
return (hitView != self.textField
&& self.textField.isFirstResponder
&& ![self.textField resignFirstResponder])
? nil : hitView;
}
#end
It turns out that hitTest:withEvent: is actually called several times (three in my testing) per touch. So you don't just want to present an alert in textFieldShouldEndEditing: if the validation fails - you need to keep track of whether the alert is already on screen. Otherwise you will see the alert pop up repeatedly.
The above method will get ugly if you have several text fields. You will have to check each one to see if it is currently first responder and not the touched view. It would be much easier if you could just ask the system for the current first responder.
There is a method that will return the current first responder: you can send firstResponder to [[UIApplication sharedApplication] keyWindow] to get the current first responder (or null if there is no first responder). Unfortunately, the firstResponder message is a public API. If you use it, you might not be allowed into the App Store.
If you decide you want to use it, here's what you put in MyView.m:
MyView.m (revised)
#import "MyView.h"
#protocol MyViewFirstResponderProtocol
// Have to declare the message so the compiler will allow it.
- (UIResponder *)firstResponder;
#end
#implementation MyView
#synthesize textField;
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
NSLog(#"hitTest for event %#", event);
UIView *hitView = [super hitTest:point withEvent:event];
UIResponder *firstResponder = [(id)self.window firstResponder];
return (firstResponder
&& hitView != firstResponder
&& ![firstResponder resignFirstResponder])
? nil : hitView;
}
#end
Set a delegate to your text field and implement the textFieldDidEndEditing:
method (in a class that conforms to the UITextFieldDelegate protocol) and call
your function in it:
void
do_something(void)
{
return;
}
/* ... */
- (void)textFieldDidEndEditing:(UITextField *)textField
{
do_something();
}
Try overriding resignFirstResponder and performing your validation there.
Let me start off by saying that this is my first real Cocoa app. It's a simple app that pretty much displays my website in a borderless window. The way I'm currently creating a borderless window is using the following:
- (void) awakeFromNib {
[window setStyleMask:NSBorderlessWindowMask];
[window setAcceptsMouseMovedEvents:YES];
[window setMovableByWindowBackground:YES];
[window setLevel:NSNormalWindowLevel];
}
The problem with this is that as a result, the WebView within the window does not pass mouse over events to elements on the loaded page, nor does it provide the ability to type in text fields. I know that I'm supposed to create a custom window instead and move the contentView into it but I'm too new to Objective-C to figure out how.
I've also tried declaring all of these with no luck:
#implementation specikAppDelegate
#synthesize window;
#synthesize webView;
- (BOOL) canBecomeKeyWindow { return YES; }
- (BOOL) canBecomeMainWindow { return YES; }
- (BOOL) acceptsFirstResponder { return YES; }
- (BOOL) becomeFirstResponder { return YES; }
- (BOOL) resignFirstResponder { return YES; }
...
#end
Additionally, I'd like to be able to move the window by clicking and dragging it anywhere but that's a side thought. I've searched extensively online, and cannot find a solution to this.
Contents of my .h file (just in case):
#interface specikAppDelegate : NSObject <NSApplicationDelegate> {
IBOutlet NSWindow *window;
IBOutlet WebView *webView;
}
#property (assign) IBOutlet NSWindow *window;
#property (nonatomic, retain) IBOutlet WebView *webView;
- (IBAction)openAboutPanel:(id)sender;
#end
Any help would be appreciated, and like I said, I'm super new to the world of Objective-C and Cocoa, but I do come from a PHP development background.
As explained in this answer, windows without title or resize bar (including borderless windows) cannot become key windows.
You were right about overriding -canBecomeKeyWindow, but you’ve missed the correct place. You shouldn’t do it in your application delegate. You need to create an NSWindow subclass and then override that method.
This sample code of apple should give you the information you need, its really easy to change the way it works and change it into your own drawn NSWindow ( without a border :D )
http://developer.apple.com/library/mac/#samplecode/RoundTransparentWindow/Introduction/Intro.html