Reading through the documentation here.
I know how to successfully setup PAN gestures for a C4Object. How would I disable a PAN gesture though?
Using...
[object setUserInteractionEnabled:NO]
... disables all gestures including TAP events and...
object.gestureRecognizers = NO
... doesn't allow me to reinitialize PAN gestures.
If anyone could share with me how disable PAN gestures (toggle PAN on/off) without effecting other gesture events it would be greatly appreciated.
You can get access to the gestures that you add to an object by using the gestureForName: method, which returns a UIGestureRecognizer object. From there, you can interact with that gesture recognizer and change its properties directly.
To toggle on/off a gesture recognizer, all you have to do is change the value of its enabled property.
The following works for me:
#import "C4WorkSpace.h"
#implementation C4WorkSpace {
UIGestureRecognizer *gesture;
C4Shape *square, *circle;
}
-(void)setup {
square = [C4Shape rect:CGRectMake(0, 0, 100, 100)];
square.center = self.canvas.center;
circle = [C4Shape ellipse:square.frame];
circle.center = CGPointMake(square.center.x, square.center.y + 200);
[self listenFor:#"touchesBegan" fromObject:circle andRunMethod:#"toggle"];
[self.canvas addObjects:#[square, circle]];
[square addGesture:PAN name:#"thePan" action:#"move:"];
gesture = [square gestureForName:#"thePan"];
}
-(void)toggle {
gesture.enabled = !gesture.isEnabled;
if(gesture.enabled == YES) square.fillColor = C4GREY;
else square.fillColor = C4RED;
}
#end
The key part of this example is the following:
[square addGesture:PAN name:#"thePan" action:#"move:"];
gesture = [square gestureForName:#"thePan"];
Notice, in the implementation there is a UIGestureRecognizer variable called gesture. What we do on the second line is find the PAN gesture associated with the square object and keep a reference to it.
Then, whenever we toggle by touching the circle we do the following:
gesture.enabled = !gesture.isEnabled;
That is, if the gesture is enabled then disable it (and vice-versa).
You can check out more on the UIGestureRecognizer Class Reference
Related
Cocos2d is prety new for me so i don't know what i should do with this situation:
I want to make a game thats something like risk. Now i made a background image like a world map (just to test). and on this map i want a swipe gesture so i can move accross the map on my ipad ( the map is prety big so i want to swipe it arround).
My problem is i don't know what the objects are called i should use. And how i can implement the gestures the best way (do i need to calculate the movement myself?).
Thanks!
Stefan.
You could maybe connect UIKit's Pan Gesture Recognizer to CCDirector's view and handle pan gesture in CCLayer class. In that way, you could have handle method which moves background with every pan movement. (Code with cocos2d 1.0.1, similar could be done with 2.0 version)
UIPanGestureRecognizer* pan = [[[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePanGesture:)] autorelease];
CCDirector* director = [CCDirector sharedDirector];
[[director openGLView] addGestureRecognizer:pan];
Handler method would look like this:
- (void)handlePanGesture:(UIGestureRecognizer*)gestureRecognizer {
// If there is more than one pan gesture recognizer connected with this method, you should remember pan and check if gestureRecognizer is equal to pan
switch (gestureRecognizer.state) {
case UIGestureRecognizerStateBegan: {
// Do something that needs to be done when pan gesture started
break;
}
case UIGestureRecognizerStateChanged: {
// Get pan gesture recognizer translation
CGPoint translation = [(UIPanGestureRecognizer*)gestureRecognizer translationInView:gestureRecognizer.view];
// Invert Y since position and offset are calculated in gl coordinates
translation = ccp(translation.x, -translation.y);
// Here you should move your background, probably in oposite direction of translation vector, something like
background.position = ccp(background.position.x - translation.x, background.position.y - translation.y);
// Refresh pan gesture recognizer
[(UIPanGestureRecognizer*)gestureRecognizer setTranslation:CGPointZero inView:gestureRecognizer.view];
break;
}
case UIGestureRecognizerStateEnded: {
// Do some work that should be done after panning is finished
break;
}
default:
break;
}
}
I think you're looking for this to add an object:
CCSprite *objectName = [CCSprite spriteWithFile:#"fileName.png"];
[self addChild:objectName];
By default, I believe the object will be in the bottom left corner.
in ios 5 i was able to disable the double tap zoom by just overriding it with a new double tap gesture. But it seems that the double tap gesture is no longer in the gesturerecognizer array that comes with the mkmapview.
NSArray *gestureRecognizers = [_mapView gestureRecognizers];
for (UIGestureRecognizer *recognizer in gestureRecognizers) {
NSLog(#"%#", recognizer);
}
returns nothing in ios 6, where in ios 5 it would return 2 recognizers, one for single tap and one for double tap.
I'd look through the gesture recognizers of MKMapView's subviews. It's probably still there somewhere.
Of course, messing around with another view's GRs is slightly dubious and will likely break the next time Apple changes something about MKMapView...
EDIT: For the benefit of anyone else reading this, please check that it's a UITapGestureRecognizer and that numberOfTapsRequired == 2 and numberOfTouchesRequired == 1.
Also, instead of disabling double-taps on the map entirely, consider adding a double-tap GR on the annotation and then do [mapDoubleTapGR requireGestureRecognizerToFail:annotationDoubleTapGR]. Again, hacky — don't blame me if it breaks on the next OS update!
This worked for me:
[_mapView.subviews[0] addGestureRecognizer:MyDoubleTapOverrider];
Do you want to let the user do anything with the view? If not, it sufficient to set userInteractionEnabled to NO. If so, what specific interactions do you need to allow? Everything but double-tapping? Why disable that one interaction?
The more we know about your use case, the better the answers we can provide.
This works for me:
//INIT the MKMapView
-(id) init{
...
[self getGesturesRecursive:mapView];
...
}
And then let the recursive function loop through the subviews and find the GR:s.
-(void)getGesturesRecursive:(UIView*)v{
NSArray *gestureRecognizers = [v gestureRecognizers];
for (UIGestureRecognizer *recognizer in gestureRecognizers) {
if ([recognizer isKindOfClass:[UITapGestureRecognizer class]]) {
[v removeGestureRecognizer:recognizer];
}
}
for (UIView *v1 in v.subviews){
[self getGesturesRecursive:v1];
}
}
This example removes all tap-GR:s. But I guess you can specify to remove whatever you'd like.
You can use a long tap gesture instead, that works.
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 :)
Hopefully someone can help with this issue. I have a class derived from UIScrollView and I'd like to prevent the user from being able to zoom or scroll via manual pinch and swipe gestures. All view navigation will instead be controlled by programmatic means in response to where a user taps (think of an ebook reader where tapping on the left or right sides of the display causes the view to scroll by exactly one page width). Any suggestions on how to implement this?
On your - (void)viewDidLoad; you should be able to just disable whatever gesture recognizer you want. In this case:
UIPinchGestureRecognizer *pinchRecognizer = self.pinchGestureRecognizer;
pinchRecognizer.enabled = NO;
or
UIPanGestureRecognizer *panRecognizer = self.scrollView.panGestureRecognizer;
panRecognizer.enabled = NO;
I sometimes do this from view controllers that contain UIScrollViews. I just target the scroll view (self.scrollView.pinchGestureRecognizer) and temporarily disable gestures when the app. is in a certain state.
To prevent user-controller zooming and panning but still allow programmatic zooming and panning of a scrollview, the best approach is to override the UIScrollView's -addGestureRecognizer: method in a subclass.
In my use I wanted to block all the recognizers and control the viewable area completely from my view controller, I did so like this:
-(void)addGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer {
//Prevent any of the default panning and zooming controls from working
gestureRecognizer.enabled = NO;
[super addGestureRecognizer:gestureRecognizer];
}
Each gesture recognizer is simply disabled, for finer control (allowing the pan control but only allow zooming via a double tap for instance) you'd simply check the incoming gesture recognizer via -isKindOfClass: and disabling as appropriate.
-(void)addGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer {
//Prevent zooming but not panning
if ([gestureRecognizer isKindOfClass:[UIPinchGestureRecognizer class]]) {
gestureRecognizer.enabled = NO;
}
[super addGestureRecognizer:gestureRecognizer];
}
I used this method in a comic reading app that uses guided navigation to animate between cropped panels on a page with the full page being contained in a UIScrollView.I can smoothly zoom in and out on a selected area by simply setting the view bounds to the region I want to display.
A quick note here. It seems UIScrollView's panGestureRecognizer and pinchGestureRecognizer are both enabled the first time a view controller is added to a window.
Basically what that means is setting them to enabled = NO in viewDidLoad won't work in some cases. I moved my enabled = NO to viewWillAppear: and it stuck. :)
I don't have too much experience with UIScrollViews, but looking at the docs, it looks like you can set maximumZoomScale, minimumZoomScale, and scrollEnabled to disable everything you want to disable.
Here are the docs: http://developer.apple.com/library/ios/#documentation/uikit/reference/UIScrollView_Class/Reference/UIScrollView.html
From the docs:
scrollEnabled:
If the value of this property is YES ,
scrolling is enabled, and if it is NO
, scrolling is disabled. The default
is YES.
When scrolling is disabled, the scroll
view does not accept touch events; it
forwards them up the responder chain.
maximumZoomScale:
This value determines how large the
content can be scaled. It must be
greater than the minimum zoom scale
for zooming to be enabled. The default
value is 1.0.
In your UIScrollView subclass overwrite also the setZoomScale: method which automatically re-disables the gesture
- (void)setZoomScale:(CGFloat)zoomScale {
[super setZoomScale:zoomScale];
self.pinchGestureRecognizer.enabled = NO;
}
In one of my iPhone projects, I have three views that you can move around by touching and dragging. However, I want to stop the user from moving two views at the same time, by using two fingers. I have therefore tried to experiment with UIView.exclusiveTouch, without any success.
To understand how the property works, I created a brand new project, with the following code in the view controller:
- (void)loadView {
self.view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 460)];
UIButton* a = [UIButton buttonWithType:UIButtonTypeInfoDark];
[a addTarget:self action:#selector(hej:) forControlEvents:UIControlEventTouchUpInside];
a.center = CGPointMake(50, 50);
a.multipleTouchEnabled = YES;
UIButton* b = [UIButton buttonWithType:UIButtonTypeInfoDark];
[b addTarget:self action:#selector(hej:) forControlEvents:UIControlEventTouchUpInside];
b.center = CGPointMake(200, 50);
b.multipleTouchEnabled = YES;
a.exclusiveTouch = YES;
[self.view addSubview:a];
[self.view addSubview:b];
}
- (void)hej:(id)sender
{
NSLog(#"hej: %#", sender);
}
When running this, hej: gets called, with different senders, when pressing any of the buttons - even though one of them has exclusiveTouch set to YES. I've tried commenting the multipleTouchEnabled-lines, to no avail. Can somebody explain to me what I'm missing here?
Thanks,
Eli
From The iPhone OS Programming Guide:
Restricting event delivery to a single view:
By default, a view’s exclusiveTouch property is set to NO. If you set
the property to YES, you mark the view so that, if it is tracking
touches, it is the only view in the window that is tracking touches.
Other views in the window cannot receive those touches. However, a
view that is marked “exclusive touch” does not receive touches that
are associated with other views in the same window. If a finger
contacts an exclusive-touch view, then that touch is delivered only if
that view is the only view tracking a finger in that window. If a
finger touches a non-exclusive view, then that touch is delivered only
if there is not another finger tracking in an exclusive-touch view.
It states that the exclusive touch property does NOT affect touches outside the frame of the view.
To handle this in the past, I use the main view to track ALL TOUCHES on screen instead of letting each subview track touches. The best way is to do:
if(CGRectContainsPoint(thesubviewIcareAbout.frame, theLocationOfTheTouch)){
//the subview has been touched, do what you want
}
I was encountering an issue like this where taps on my UIButtons were getting passed through to a tap gesture recognizer that I had attached to self.view, even though I was setting isExclusiveTouch to true on my UIButtons. Upon reviewing the materials here so far, I decided to put some code in my tap gesture code that checks if the tap location is contained in any UIButton frame and if that frame is also visible on the screen at the same time. If both of those conditions are true, then the UIButton will already have handled the tap, and the event triggered in my gesture recognizer can then be ignored as a pass through of the event. My logic allows me to loop over all subviews, checking if they are of type UIButton, and then checking if the tap was in that view and the view is visible.
#objc func singleTapped(tap: UITapGestureRecognizer)
{
anyControlsBreakpoint()
let tapPoint = tap.location(in: self.view)
// Prevent taps inside of buttons from passing through to singleTapped logic
for subview in self.view.subviews
{
if subview is UIButton {
if pointIsInFrameAndThatFrameIsVisible(view: subview, point: tapPoint)
{
return // Completely ignores pass through events that were already handled by a UIButton
}
}
}
Below is the code that checks if point was inside a visible button. Note that I hide my buttons by setting their alpha to zero. If you are using the isHidden property, your logic might need to look for that.
func pointIsInFrameAndThatFrameIsVisible(view : UIView, point : CGPoint) -> Bool
{
return view.frame.contains(point) && view.alpha == 1
}