I'm having some troubles reading trackpad events. I need to catch a single tap and a drag of one finder on trackpad. And also to distinguish them. Catching a single tap is working like this:
- (void)mouseUp:(NSEvent*)theEvent
{
CGFloat wdev2 = self.bounds.size.width / 2;
CGFloat hdev2 = self.bounds.size.height / 2;
NSPoint point = [theEvent locationInWindow];
float x = (point.x - wdev2) / wdev2;
float y = (point.y - hdev2) / hdev2;
[_touchHandler handleMouseTouch:x And:y];
}
but how to recognize a drag? I tried mouseDragged: and that's giving me three finder pan event.
Thanks in advance.
I believe you'll want to use mouseDragged in addition to NSGestureRecognizer.
mouseDragged:
The default implementation of this method does nothing. Use this
method to update the state of your gesture recognizer in whatever way
is appropriate.
A gesture recognizer monitors events that occur in its view (and any
subviews) but does not take part in the responder chain itself. The
gesture recognizer receives events before any views do. Use the
delaysPrimaryMouseButtonEvents property to control whether event is
propagated to the view.
Used with NSGestureRecognizer you should be able to get your desired effect.
↳ AppKit Framework Reference > NSGestureRecognizer Class Reference
Related
I have a pan gesture recognizer to drag a panel up, down, left, or right. When the direction of the pan is not possible I don't allow the recognizer to begin so that the touches can go to other UI elements within the panel.
However, on iOS7 the translation sometimes gets reset between gestureRecognizerShouldBegin: and my gesture handler handlePan:
- (BOOL)gestureRecognizerShouldBegin:(UIPanGestureRecognizer*)panGR
{
CGPoint trans = [panGR translationInView:self.view];
NSLog(#"should begin trans: (%.2f, %.2f)", trans.x, trans.y);
...
This logs: should begin trans: (18.00, 0.00)
- (void)handlePan:(UIPanGestureRecognizer*)panGR
{
CGPoint trans = [panGR translationInView:self.view];
switch(panGR.state)
{
case UIGestureRecognizerStateBegan:
NSLog(#"handlePan began trans: (%.2f, %.2f)", trans.x, trans.y);
...
This logs: handlePan began trans: (0.00, 0.00)
Which means the shared code to determine the direction of the pan (right, in this case) works in gestureRecognizerShouldBegin: and allows the gesture to begin, but then can't be determined in handlePan: when the state is UIGestureRecognizerStateBegan.
Is this a bug in iOS7 or has the behaviour deliberately changed to accommodate new gesture types? Also, can anyone suggest a good way to work around this issue?
The UIPanGestureRecognizer is always setting the translation to (0,0) after reaching the UIGestureRecognizerStateBegan state - since even the slightest translation is recognized it's designed to work as a trigger only (if you set up large threshold for beginning the recognition like (50,50) you will obviously get a "lag" in UI behavior - as a workaround I would suggest to store the value of the translation and then use a UIView animation to pan the object more smoothly). You should use the UIGestureRecognizerStateChanged for updating the translation, and UIGestureRecognizerStateRecognized for setting the end point of the pan.
You can determine the direction in handlePan: by checking if the horizontal translation is higher or lower than 0. But at the end of handlePan: you do need to reset the CGPoint of the translation as the new reference for the next translation.
- (void)handlePan:(UIPanGestureRecognizer*)panGR
{
CGPoint translation = [gr translationInView:self.view];
if (translation.x > 0) {
//Direction: Right
} else if (translation.x < 0) {
//Direction: Left
}
[gr setTranslation:CGPointZero
inView:self.view];
}
I'm creating a scroll view for displaying a very large view, and I need both scroll and zoom functionality (just like an image viewer). Here are the steps that I've taken:
In interface builder, I've put a scroll viewer to the view controller.
I've added a pinch gesture recognizer to the scroll viewer.
I've connected the gesture recognizer's action to the code to handle the gesture events.
When the view controller is loaded, I change my view's origin to the center (viewer is my scroll viewer): self.viewer.contentOffset = CGPointMake(384, 512);
In my code for the handler, I handled the event as such:
(startScale is 1.0 in the beginning)
- (IBAction)handlePinch:(UIPinchGestureRecognizer *)sender {
if(sender.state == UIGestureRecognizerStateEnded){
startScale *= sender.scale;
}else{
float result = sender.scale * startScale;
self.viewer.transform = CGAffineTransformMakeScale(result, result);
}
}
When I run the app, the gesture is recognized and scaling works correctly, however, the whole view scales with respect to the 0,0 point of the screen (top left). I want it to scale with respect to the middle point that I'm applying the gesture, just as a natural pinch gesture for zooming into a photo.
I've also tried setting self.viewer.frame's origin, but nothing changed. I've searched about the problem and found these:
How to set a UIView's origin reference? (already tried)
https://stackoverflow.com/questions/13163279/pinch-punch-gestures-center (about my problem, but unanswered)
UIPinchGestureRecognizer for zooming and panning an image in xcode (looks like an overkill, too complicated for me, and I'm not sure if this would really help my situation)
How can I achieve natural pinching with my scroll view, what am I doing wrong?
Thanks,
Can.
Well, the answer to the problem is very simple: Remove the pinch gesture altogether. The benefit of using a UIScrollView is that it handles the panning/zooming internally, and you have to do nothing
Edit: To make sure the content is scaled properly, you are going to need a UIView (called contentView or whatever you want) where you put all the content, and then on the delegate method of your UIScrollView do this:
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
return contentView;
}
This should solve your problem
Edit 2: Also remember to set the minimum / maximum zoom scales for your UIScrollView
I have a view (it's the view that is zoomed in a scrollview) that I am trying to make only stretch in the x direction when the user pinches or double-taps to zoom. This view is being constantly drawn on: up to 10 times per second, using Core Graphics to draw a graph.
So I override setTransform like so:
- (void)setTransform:(CGAffineTransform)newValue;
{
CGAffineTransform constrainedTransform = CGAffineTransformIdentity;
constrainedTransform.a = newValue.a;
[super setTransform:constrainedTransform];
}
And this generally gives me the behavior I want, except for one big problem. When the view is being drawn on very often and the user double taps to zoom in, the whole view will often just go black. This happens very rarely if I don't override the above method (although it still does happen once in a while). Also interesting is that when the user zooms using a pinch gesture, this does not happen. This is the function triggered by the double tap:
- (void)handleDoubleTap:(UIGestureRecognizer *)gestureRecognizer
{
CGRect frame = [[self tiledScrollView] frame];
float newScale = [[self tiledScrollView] zoomScale] * kZoomStep;
CGRect zoomRect = [self zoomRectForFrame:frame withScale:newScale withCenter:[gestureRecognizer locationInView:[[self tiledScrollView] tileContainerView]]];
[[self tiledScrollView] zoomToRect:zoomRect animated:YES];
}
And the pinch gestures are just handled by UIScrollView's stock pinch recognizer. One thing that does bother me about the above function is that zoomRect is scaled in both the x and y directions even though my view only scales in one direction. I have tried scaling zoomRect in only the x direction and then calling zoomToRect, but then the graph won't zoom.
So it seems that UIScrollView's pinch recognizer and my double tap recognizer scale the view in two different ways, and only the pinch recognizer's way works with my code... Also, this problem becomes very rare when the drawing rate is slowed, and nonexistent when there is no drawing going on. I've tried using queues to make drawing and zooming sequential but I haven't had much luck with that. I suspect that zoomToRect and other zooming methods may dispatch tasks off to other threads or whatever so I don't think they can be sequentialized that way. But is this something I should look into more?
Any help would be greatly appreciated. I've wasted days trying to fix this already -_- Thanks
I am building a venue layout app for the ipad that will use different classes of objects (tables /chairs/ stage segments etc) for the user to add to the floor plan and adjust.
In other apps I have used UIgestures for the panning and rotation of objects:
- (IBAction)handlePan:(UIPanGestureRecognizer *)recognizer {
CGPoint translation = [recognizer translationInView:self.view];
recognizer.view.center = CGPointMake(recognizer.view.center.x + translation.x, recognizer.view.center.y + translation.y);
[recognizer setTranslation:CGPointMake(0, 0) inView:self.view];
}
- (IBAction)rotateMe:(UIRotationGestureRecognizer *)recognizer{
recognizer.view.transform = CGAffineTransformRotate(recognizer.view.transform, recognizer.rotation);
recognizer.rotation = 0;
}
However the objects for this app will be smaller than normal fingers will allow for multiple touch gestures.
I would like to opt for a "Rotate" button within the view that when pressed and held will allow a single touch on anyobject to custom rotate it.
Or if anyone can guide me to a better option?
There's a project on GitHub called KTOneFingerRotationGestureRecognizer that is a custom rotation gesture recognizer that uses a single finger to do rotations. You touch down on an object and spin it as if it had an axle in it's center. It might work for you, depending on how small your shapes are. You might need to make your views a little larger the the images they contain (add some padding, in other words.)
Should be fairly simple to do. A touch of the rotate button will toggle rotation on and pressing another object will perform the rotation. Use CGAffineTransformMakeRotation to rotate.
yourObject.view.transform=CGAffineTransformMakeRotation(yourVariable*M_PI/180.0);
Increment yourVariable by whatever angle you choose and it should rotate a bit each time, i.e incrementing by 90 would rotate the object 90 degrees each time you touch the object.
I have a (vertical) UISlider inside a UIScrollview. I'd like to be able to change the value of the slider, and, without lifting my finger, scroll the scrollview left or right.
Desired behavior:
Touch down inside vertical UISlider, followed by a finger drag left or right causes the scrollview to scroll
Actual behavior:
Touch down inside vertical UISlider, followed by a finger drag left or right causes no movement in UIScrollview. A touch down outside the UISlider followed by a drag will scroll the scrollview as expected
UIView has a property called exclusiveTouch which seems as if it might be related to my problem. I tried setting it to NO, with no luck.
So, how can is set up my UISliders so that the scrollview beneath them will respond to touches which originate inside the UISliders?
Have you tried subclassing UIScrollView and implementing - (BOOL)touchesShouldCancelInContentView:(UIView *)view? According to the Apple documentation:
// called before scrolling begins if touches have already been delivered to a subview of the scroll view. if it returns NO the touches will continue to be delivered to the subview and scrolling will not occur
// not called if canCancelContentTouches is NO. default returns YES if view isn't a UIControl
If you simply return NO if the view is your UISlider, this may do what you want, assuming your UIScrollView only scrolls horizontally. If this doesn't work, you likely will have to do custom touch handling (ie. overriding touchesBegan:withEvent:, touchesChanged:withEvent:, etc.) for both your UIScrollView and your UISlider.
What you are seeing is the intended behavior.
Each touch event only gets handled by one control. What exclusiveTouch does is actually to prevent other touch events from being delivered to other views.
To do what are trying to do you would have to do some of the touch handling yourself. Passing the event to both your views. You could do either do it by implementing all the touchesBegan:, touchesMoved: etc. methods and pass the events to both views. You can read more about that approach in the UIResponder documentation. Another approach is to do the event handling in a UIGestureRecognizer on the scroll view that hit tests the slider and updates the value of the slider using the y-delta. You can read more about gesture recognizers and event handling in the section about Gesture Recognizers in the Event Handling Guide for iOS.
Side note:
Go to the Settings app and toggle a switch half way (for example the Airplane mode toggle) and then drag down. Nothing will happen. The rest of the OS behaves the same way. Are you sure that this is the interaction that you really want to do? Apps that behave differently often feel weird and unfamiliar.
Your question confused me a bit. You are saying a vertical slider - but dragging left and right?
If you wish to scroll the scrollview when dragging the UISlider, the proper way to do so is
[mySlider addTarget:self action:#selector(sliderMoved:) forControlEvents:UIControlEventValueChanged];
and
- (void) sliderMoved:(UISlider*) slider {
myScrollView.contentOffset.x = slider.value * (myScrollView.contentSize.width - myScrollView.bounds.size.width);
}
Hope this is what you want.
You need to set delaysContentTouches as NO and prevent for UISlider objects to scroll, Check below code.
mySlider.delaysContentTouches = NO;
- (BOOL)touchesShouldBegin:(NSSet *)touches withEvent:(UIEvent *)event inContentView:(UIView *)view {
if ([view isKindOfClass:[UISlider class]])
{
UITouch *touchEvent = [[event allTouches] anyObject];
CGPoint locationEvent = [touchEvent locationInView:view];
CGRect thumbRect;
UISlider *mySlide = (UISlider*) view;
CGRect trackRect = [mySlide trackRectForBounds:mySlide.bounds];
thumbRect = [mySlide thumbRectForBounds:mySlide.bounds trackRect:trackRect value:mySlide.value];
if (CGRectContainsPoint(thumbRect, locationEvent))
return YES;
}
return NO;
}
I think you can get some reference from this example in this example it is shown that how to cancel any touch or any gesture recognizers and apply them to other views.
May this lead you to the solution of your problem and if it will just let me know about it
Happy Codding :)