I'm trying to implement a shake recognition that works throughout my application. To do this I'm adding the following code to my xxxAppDelegate.m:
-(BOOL)canBecomeFirstResponder {
return YES;
}
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event {
if (event.type == UIEventSubtypeMotionShake) {
NSLog(#"Shaken, not stirred.");
}
}
But because in the .h file the delegate is defined as a
#interface xxxAppDelegate : NSObject <UIApplicationDelegate, UITabBarControllerDelegate>
I cannot use the
[self becomeFirstResponder];
in the .m to make the app delegate the first responder. Therefore of course it doesn't work.
What would be the best way to get it working?
What happens if you change the app delegate to a subclass of UIResponder?
Edit
You can read about the responder chain here in the documentation.
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.
Alright, my mistake, I had previously believed that my ivar was not changing correctly. I was wrong (and got a few downvotes for it). Sorry, the real issue is that i've been calling it incorrectly. I am now posting all the code and i'll let you look at it.
My First class:
classOne.h:
#interface DetailPageController : UIPageViewController
{
BOOL isChromeHidden_;
}
- (void)toggleChromeDisplay;
#end
classOne.m:
#interface DetailPageController ()
#end
#implementation DetailPageController
- (void)toggleChromeDisplay
{
[self toggleChrome:!isChromeHidden_];
}
- (void)toggleChrome:(BOOL)hide
{
//Find chrome value
isChromeHidden_ = !isChromeHidden_;
NSLog(isChromeHidden_ ? #"YES" : #"NO");
}
#end
From the comment's I have received I believe none of that is the actual issue, it's in the following.
classTwo.h: (nothing declared)
classTwo.m:
#interface classTwo ()
#end
#implementation classTwo
//Touches Control
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
if ([touch view]) {
if ([touch tapCount] == 2) {
NSLog(#"double touched");
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(toggleChromeDisplay) object:nil];
}
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
if ([touch view]) {
if ([touch tapCount] == 1) {
NSLog(#"single touch");
[self performSelector:#selector(toggleChromeDisplay) withObject:nil afterDelay:0.5];
}
}
}
- (void)toggleChromeDisplay
{
DetailPageController *pageController = [[DetailPageController alloc] init];
[pageController toggleChromeDisplay];
}
#end
Again, sorry for the previous post, I had thought it was an issue with the method but is in fact the way I am calling it.
What I have been forced to do is implement the touching in the controller which handles the area touched but I have the method for the chrome (nav bar and toolbar) in another.
Overall Question
Why is it that every time I call my toggleChromeDisplay method in classTwo, I always get the same NO from my ivar in classOne?
What i've tried for classTwo.h:
#import "DetailPageController.h"
#interface classTwo : UIViewController
{
DetailPageController *detailPageController_;
}
#property (nonatomic, assign) DetailPageController *detailPageController;
#end
My modified code:
In my classTwo.h:
#import "DetailPageController.h"
#interface PhotoViewController : UIViewController
{
DetailPageController *detailPageController_;
}
#property (nonatomic, strong) DetailPageController *detailPageController;
#end
classTwo.m:
#import "classTwo.h"
#interface classTwo ()
#end
#implementation classTwo
#synthesize detailPageController = detailPageController_;
//Touches Control
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
if ([touch view]) {
if ([touch tapCount] == 2) {
NSLog(#"double touched");
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(toggleChromeDisplay) object:nil];
}
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
if ([touch view]) {
if ([touch tapCount] == 1) {
NSLog(#"single touch");
[self performSelector:#selector(toggleChromeDisplay) withObject:nil afterDelay:0];
}
}
}
- (void)toggleChromeDisplay
{
[self.detailPageController toggleChromeDisplay];
}
#end
classTwo always instantiates a completely brand new instance of DetailPageViewController before calling toggleChromeDisplay on it, and then it allows it to go out of scope and presumably is deallocated. You need to have it use an instance that's actually alive in your program and controls views in the view hierarchy.
You could add a DetailPageViewController property to classTwo and then your -[classTwo toggleChromeDisplay] implementation would look like:
- (void)toggleChromeDisplay
{
[self.detailPageController toggleChromeDisplay];
}
Again, just make sure to assign to that property the instance of the view controller that's actually on screen. alloc and init are used to creating brand new instances of objects, which won't be the ones that already exist in your application if they've been loaded up, for instance, from a storyboard. So, in your application you probably have a DetailPageViewController instance already that is doing things on the screen and controlling interactions with the user - but your classTwo never is able to message it because again it's creating entirely separate instances of that class. So you need to determine where in your application the DetailPageViewController that's visible on screen is getting instantiated, and at that point ensure your classTwo instance can get a reference to it.
Forgive my repetitiveness, but it is a common mistake I see on Stack Overflow. Just make sure that you understand that while there is one definition of a Class, which is where its instance variables and methods are defined, there can be many separate instances of objects that are created from it (we often say they are instantiated, or created, inited, you'll see a number of terms). Each of those objects can have different values for their instance variables (and properties), and they all have their own distinct life time from a memory-management standpoint. Calling the pair of methods alloc and init is one very common way to make a new instance of a class, that has its own lifetime and instance variables.
Finally, I'd like to suggest that you read and follow Apple's Cocoa style guide as your choice of names for methods and classes has caused confusion among your fellow developers. If you start to apply that you will find communication with others to go smoother and your problems easier to understand.
All you need is
- (void)toggleChromeDisplay
{
isChromeHidden_ = !isChromeHidden_;
}
(based on your edit)
toggleChrome: sets the isChromeHidden_ variable, it does not toggle it. To toggle you would write:
- (void)toggleChrome
{
isChromeHidden_ = !isChromeHidden_;
//Find chrome value
NSLog(isChromeHidden_ ? #"YES" : #"NO");
}
!isChromeHidden_ is the opposite value of isChromeHidden_.
I copied your exact code into my project and it is switching between YES and NO as expected. This tells me that your code that you have posted is in fact correct and you are either calling it incorrectly or you are setting the variable somewhere else as well.
I just started reading up on Objective-C yesterday, and i can't quite figure why my
textFieldShouldReturn
method isn't being run.
This is the actual method:
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
self.itemTxt.text = #"Return pressed";
return YES;
}
This is the interface line in my header file:
#interface ArrViewController : UIViewController <UITextFieldDelegate>
You need to actively set the delegate for the UITextField:
For example, in viewDidLoad you could write:
myTextField.delegate = self;
You can also hook this up in Interface Builder if desired.
Use setDelegate method of your textfield to self
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.
[UPDATED AFTER FIRST AWNSER]
I have tried to find a way to use and implement the keyDown option in Objective C. But when I try it, it always fails...
Can anyone give me an example of how this is done. I understand Objective C good and a full explaination is not needed.
I deleted the method in -(void) keyDown because it wasn't working.
This is my code now:
#import <Cocoa/Cocoa.h>
#interface ViewController : NSView {
IBOutlet id pressLabel;
}
#end
#import "ViewController.h"
#implementation ViewController
-(BOOL) acceptsFirstResponder
{
return YES;
}
-(BOOL) becomeFirstResponder
{
return YES;
}
-(BOOL) resignFirstResponder
{
return YES;
}
-(void)keyDown:(NSEvent *)theEvent
{
NSString *theUpArrow = [NSString stringWithFormat:#"%c",NSUpArrowFunctionKey];
if( [[theEvent characters] isEqualToString:theUpArrow]){
[pressLabel setStringValue:#"Pressed"];
} else {
[super keyDown:theEvent];
}
}
#end
keyDown: is an NSResponder method, typically implemented in views. This class is named Controller, which would suggest it isn't a view, and thus won't receive key down events. You probably want to put it in a view instead.
[self keyDown:theEvent];
This doesn't solve your problem, but I think for the line above, you want to use super and not self. If you use self it will invoke the same method again, and it will keep invoking the same method over and over, eventually crashing your application once there is no more stack space.