How to differentiate side and wheel buttons with cocoa? - objective-c

I have mouse with 5 buttons. They are left, right, wheel and 2 side buttons (back/forward). Is there any way to check, whether side button ("back" for example) was pressed? I learning cocoa's documentation about mouse event. At the moment I know that there is - (void)otherMouseDown:(NSEvent *)event; method, which is called whenever other (not right/left) button was pressed. But documentation tells nothing about additional information about which button was actually pressed. How to overcome this?

-(void)scrollWheel:(NSEvent *)event; method of NSResponder and so also from NSViews
- (void)rightMouseDown:(NSEvent *)event; same way
- (void)otherMouseDown:(NSEvent *)event as you know
but also..
-(void)someAction:(id)sender
{
NSEvent *event = [NSApp currentEvent];
if (event.type == NSEventTypeLeftMouseUp ||
event.type == NSEventTypeLeftMouseDown)
{
if (event.clickCount == 2) {
// doubleClick
}
}
else if (event.type == NSEventTypeOtherMouseDown)
{
if (event.buttonNumber==4)
{
//i think first button is 0, so 5 should be 4.
}
}
}
AppKit's NSEvent.h has everything you need just check the type of the event first and then go for buttonNumber and what ever you choose

Related

Apple TV App not exiting to the home screen from initial view controller when menu button pressed on remote

I've just had an Apple TV app rejected because of
'In addition, the Menu button on the Siri Remote does not behave as expected in your app.
Specifically, when the user launches the app and taps the Menu button on the Siri Remote, the app does not exit to the Apple TV Home screen.'
I'm looking this up and from what I can tell this should be the automatic behaviour of pressing the menu button when on the initial view controller. However I have a navigation controller with a root view controller, instantiated automatically via the storyboard with no methods overridden and nothing happens when I press the menu button on this view controller.
Can someone tell me if I'm missing something or if there's a way of implementing this manually?
I'm thinking I could just intercept the menu button press and call exit(0), but that doesn't seem like a graceful way of exiting.
I just had an app rejected for this reason too. In my case the problem was overriding the press<Phase>d:withEvent group of methods without calling the super implementation.
So I changed this:
-(void)pressesBegan:(NSSet*)presses withEvent:(UIPressesEvent *)event {
// my code
}
To this:
-(BOOL)ignoreMenu:(NSSet*)presses {
return ((UIPress *)[presses anyObject]).type == UIPressTypeMenu;
}
-(void)pressesBegan:(NSSet*)presses withEvent:(UIPressesEvent *)event {
if ([self ignoreMenu:presses]) return [super pressesBegan:presses withEvent:event];
// my code
}
And the menu button works again. What's confusing is that the Home button continues to work whether you call the super or not.
My complete working code as of tvOS 9.1 (compiled under Objective-C++)
bool AppRespondedToBack = false;
-(void)pressesBegan:(NSSet*)presses withEvent:(UIPressesEvent *)event
{
UIPress *UP = (UIPress *)presses.anyObject;
if ( UP && UP.type == UIPressTypeMenu )
{
//OnBack should be where you handle the back operation in your app
AppRespondedToBack = OnBack();
if ( !AppRespondedToBack )
// Let AppleTV return to the home screen
[super pressesBegan:presses withEvent:event];
}
else
[super pressesBegan:presses withEvent:event];
}
-(void)pressesEnded:(NSSet*)presses withEvent:(UIPressesEvent *)event
{
UIPress *UP = (UIPress *)presses.anyObject;
if ( UP && UP.type == UIPressTypeMenu )
{
if ( !AppRespondedToBack )
// Let AppleTV return to the home screen
[super pressesEnded:presses withEvent:event];
}
else
[super pressesEnded:presses withEvent:event];
}
In a more generic way, the Menu button should take you to the home screen only in some conditions, first one would be when you start the app and click the Menu button, and later on in your app if you are at the 'root view' of your app.
If you display something on the screen, like a popover/dialog which is not controlled by the standard sdk but manualyl by yourself (ie adding a UIView as popover), then the Menu button should just act as a 'cancel/go back' like it would do on the navigation controller.
-(void)pressesBegan/Ended:(NSSet*)presses withEvent:(UIPressesEvent *)event {
if( presses.anyObject.type == UIPressTypeMenu) {
if ( !somePopoverDisplayed) {
// Let the sdk do its thing with the menu button
[super pressesBegan:presses withEvent:event];
} else {
// otherwise handle the menu button yourself ...
}
return;
}
// handle other buttons
}
Here is the snipped of code that ended up working for me:
-(void)pressesEnded:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event {
if( presses.anyObject.type == UIPressTypeMenu) {
if (scene.view.paused == YES) {
// This will return to the Apple TV Menu
} else {
// This allows your code to run without exiting the app
[self pauseScene];
[super pressesBegan:presses withEvent:event];
}
return;
}
}
So I ended up begrudgingly calling exit(0) like so
- (void)pressesBegan:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
{
[super pressesBegan:presses withEvent:event];
if (presses.anyObject.type == UIPressTypeMenu)
{
exit(0);
}
}
It's not quite as neat as when it works automatically; you end up with a blurry white view for about half a second instead of the more graceful fade out animation. It does however work and I can confirm that I made it through Apple's review process doing it this way.
What sorted it out for me was just removing the gesture recogniser from your view (for your Menu button) when you don't need it anymore.
Then when, you have a situation where you want the Menu button to for example, go back to a previous screen, then just add your gesture recogniser again.
That way the Menu button works the way it should, without you having to call exit() or force your app to quit some other way.

Transient NSPopover swallows first click on parent window control

I have a transient NSPopover and when it's open and I click a button in the parent window the popover is dismissed instead and the click "swallowed". Only the second click on the button triggers the action properly.
Is there a way to pass the first click through to the control directly and dismiss the popover in one step?
NSPopover does seem to swallow the event for the targeted position view. Other views are fine. My solution is to get the delegate to forward the last mouse down event to the target view if hit testing reveals it was the clicked view. Unfortunatety NSApp -currentEvent is nil when the delegate gets messaged - not sure why. So I added an event monitor to the app delegate like so:
- (void)addEventMonitor
{
if (self.eventMonitor) {
return;
}
self.eventMonitor = [NSEvent addLocalMonitorForEventsMatchingMask:(NSLeftMouseDownMask) handler:^(NSEvent *incomingEvent) {
NSEvent *result = incomingEvent;
self.monitoredEvent = result;
return result;
}];
}
When the delegate closes it checks to see it the last monitored event was in the target:
- (void)popoverDidClose:(NSNotification *)notification
{
// [NSApp currentEvent] is nil here
NSEvent *event = [(BPApplicationDelegate *)[NSApp delegate] monitoredEvent];
if (event && (event.type & NSLeftMouseDown)) {
NSPoint pt = [self.targetView.superview convertPoint:event.locationInWindow fromView:nil];
if ([self.TargetView hitTest:pt]) {
[NSApp postEvent:event atStart:NO];
}
}
}

UIButton responds to UIPanGestureRecognizer on top of it

I just added a UIView with UIPanGestureRecognizer on top of my view.
The view has several UIButtons which respond to touchUpInside events.
What's weird is that ever since I brought the UIPanGestureRecognizer, when panning, if the UIButton is right underneath the "Panning view", the button would trigger which is not what I am after.
Of course I could make a BOOL flag for "panning", so that the button won't fire, but it seems to me like bad engineering and surely something I am missing. I guess after the first touch, both views intercept the event.
Is it possible to overcome this?
Thanks
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldReceiveTouch:(UITouch *)touch {
if ([touch.view isKindOfClass:[UIButton class]]) {
return NO;
}
return YES;
}
use this method to differentiate GestureRecognizer and Button Acton.
Hope this helps you.
Not sure if this help
[[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePanned:)];
- (void)handlePanned:(UIPanGestureRecognizer*)thePanner{
if (thePanner.state == UIGestureRecognizerStateChanged ){
//disable button
}else if (thePanner.state == UIGestureRecognizerStateEnded) {
//enable button
}else if ( thePanner.state == UIGestureRecognizerStateFailed ){
//enable button
}
}
you can sort-out your problem by following two tactics:-
1)When adding the gesture in your required view, be sure that it will not added with un-required view, in this case is your UIButton, &;
2)At the method/delegate where you handle the case of detecting the gesture, ie what gesture do, before the implementation you assure that this is not the UIButton.
If you insist, I'll try for sample code.
Have a good day ahead. :)

How to repeatedly perform an action when a button is continuously pressed in obj-c

I've got four simple methods, four buttons and one object.
- (IBAction)left:(id)sender{
object.center = CGPointMake(object.center.x - 5, object.center.y);
}
- (IBAction)right:(id)sender{
object.center = CGPointMake(object.center.x + 5, object.center.y);
}
- (IBAction)down:(id)sender{
object.center = CGPointMake(object.center.x, object.center.y + 5);
}
- (IBAction)up:(id)sender{
object.center = CGPointMake(object.center.x, object.center.y - 5);
}
When I press a button, the method is executed once. When the button is pressed continuously it's the same.
What do I have to do so that when I press the button continuously my object keep moving to the left?
As #Maudicus said, you probably need to do something with NSTimers to get a continuous button press triggering. The example I'm going to use is moving an object on screen (since you want to do that anyway). I've used graded moving because I have no idea whether you are writing a grid-based game and so need exactly 5 pixels of movement. Just cut out all of the stepSize code and set it to 5 if that's what you do.
Write a timer callback function which checks whether a BOOL is set and if so keeps triggering itself:
- (void)moveObjectLeft:(NSTimer *)timer
{
// check the total move offset and/or the X location of the object here
// if the object can't be moved further left then invalidate the timer
// you don't need to check whether the button is still being pressed
//[timer invalidate];
//return;
// the object moves gradually faster as you hold the button down for longer
NSNumber *moveOffset = (NSNumber *)[timer userInfo];
NSUInteger stepSize = 1;
if(moveOffset >= 40)
stepSize = 10;
else if(moveOffset >= 15)
stepSize = 5;
else if(moveOffset >= 5)
stepSize = 2;
// move the object
object.center = CGPointMake(object.center.x - stepSize, object.center.y);
// store the new total move offset for this press
moveOffset += stepSize;
[timer setUserInfo:moveOffset];
}
Create a timer property in the current classes .h:
#property (nonatomic, retain) NSTimer *moveTimer;
Synthesize it in your .m:
#synthesize moveTimer;
Create your timer object when the button is pressed. Either do this in touchesBegan:withEvent: and check it's a Touch Down event, or connect the Touch Down event in Interface Builder to an IBAction method.
NSNumber *moveOffset = [NSNumber numberWithUnsignedInt:0];
self.moveTimer =
[NSTimer
scheduledTimerWithTimeInterval:0.2
target:self
selector:#selector(moveObject:)
userInfo:moveOffset
repeats:YES];
When the button is released (using one of the above methods again, touchesEnded:withEvent: for a Touch Up Inside or probably even a Touch Up Outside, or another IBAction on both of those events), invalidate the timer externally:
[self.moveTimer invalidate];
self.moveTimer = nil;
When a button gives its mousedown event, start moving. When the button gives its mouseup event, stop moving.
Be careful; this can act funny if multiple buttons can be pressed at the same time.
I think you need to schedule a timer and repeat a method that checks the button states.
// assume timer is set up to test button states, triggering controlLoop every x seconds
-(void)controlsLoop
{
if (leftButton.state == UIControlStateSelected || leftButton.state == UIControlStateHighlighted) {
}
}
I have never done this before so have fun playing around with it.
I usually achieve controls like you want in Cocos2d,
It might be better to implement these methods
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
to set which direction you want to move the object, and still have a timer triggering the actual movement.

Obj C - resign first responder on touch UIView

I'm trying to get the keyboard to disappear when the screen is touched, a question that is answered all over stackoverflow. I was able to get the keyboard to disappear when the enter key was pressed thanks to a thread here. I'm not having luck on the background touch resigning the first responder. The method is being entered, I have an NSLog in the method saying, "in backgroundTouched" but the keyboard is still there.
I've tried making the UIView a UIControl class so I could use the touch event.
journalComment is a UITextView.
-(IBAction)backgroundTouched:(id)sender
{
[journalComment resignFirstResponder];
NSLog(# "in backgroundTouched");
}
I've also tried having a invisible button under everything that calles the backGroundTouched method. I think it maybe that I'm missing something in interface builder, but I'm not sure what.
Thank you for any help!
This is what works for the done button:
-(BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range
replacementText:(NSString *)text
{
// Any new character added is passed in as the "text" parameter
if ([text isEqualToString:#"\n"]) {
// Be sure to test for equality using the "isEqualToString" message
[textView resignFirstResponder];
// Return FALSE so that the final '\n' character doesn't get added
return FALSE;
}
// For any other character return TRUE so that the text gets added to the view
return TRUE;
}
I found the following code works best with my text view (not text field) without the delegate methods:
first you set up a tap gesture recognizer onto your view :
- (void)viewDidLoad{
UITapGestureRecognizer* tapRecognizer = [[UITapGestureRecognizer alloc]
initWithTarget:self
action:#selector(tap:)];
tapRecognizer.delegate = self;
[self.view addGestureRecognizer:tapRecognizer];
}
and then in your tap method :
- (void)tap:(id)sender
{
// use to make the view or any subview that is the first responder resign (optionally force)
[[self view] endEditing:YES];
}
this should allow your keyboard to be dismissed when you anywhere on the view.
Hope this helps
Try this. We had this problem eariler, but eventually found the right solution.
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
[yourtextfield resignFirstResponder];
// you can have multiple textfields here
}
This should resolve the problem with the keyboard not dissapearing when pushing the background.