I have five views on which i have applied UIPanGestureRecognizer placed with some distance to each other having same Y-asix from top (box type shape).
I am dragging view with panning on x-axis. The problem is that i can drag multiple views with multiple touches. i want to restrict one view dragging at a time.
I tried using UIPanGestureRecognizer property
pan.maximumNumberOfTouches=1;
but that is for just single view. Any Ideas?
Try this too,
Lets say you have all this five views added on the view of a viewController. Then do the following;
myViewController.view.multipleTouchEnabled = NO;
This make the mainView to process only first touch and only this first touch will flow down the hierarchy (i.e. to subViews and their gestureRecognizers )
Hope this works.
Alernate:
Define this in the interface of your mainView or mainViewController that holds the 5 views.
#property(nonatomic,retain)UIPanGestureRecognizer *currentRecognizer;
Then apply the following code.
-(void)on_pan_gesture:(UIPanGestureRecognizer*) panGestureRecognizer
{
if(self.currentRecognizer != nil && [self.currentRecognizer isEqual:panGestureRecognizer])
{
//do the task of the selected gesture recognizer
//this recognizer will be active till the touches are not ended or cancelled.
//hence on the first recognizer will work.
}
else
{
//if there is not currentRecognizer then set this recognizer to be current.
self.currentRecognizer = panGestureRecognizer;
}
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
self.currentRecognizer = nil;
}
-(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
self.currentRecognizer = nil;
}
Use the gesture recognizers' delegate method
gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:
to make it return NO.
Related
In Xcode 5.1 I have created a simple test app for iPhone:
The structure is: scrollView -> contentView -> imageView -> image 1000 x 1000 on the top.
And on the bottom of the single view app I have seven draggable custom UIViews.
The dragging is implemented in Tile.m with touchesXXXX methods.
My problem is: once I add a draggable tile to the contentView in my ViewController.m file - I can not drag it anymore:
- (void) handleTileMoved:(NSNotification*)notification {
Tile* tile = (Tile*)notification.object;
//return;
if (tile.superview != _scrollView && CGRectIntersectsRect(tile.frame, _scrollView.frame)) {
[tile removeFromSuperview];
[_contentView addSubview:tile];
[_contentView bringSubviewToFront:tile];
}
}
The touchesBegan isn't called for the Tile anymore as if the scrollView would mask that event.
I've searched around and there was a suggestion to extend the UIScrollView class with the following method (in my custom GameBoard.m):
- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView* result = [super hitTest:point withEvent:event];
NSLog(#"%s: %hhd", __PRETTY_FUNCTION__,
[result.superview isKindOfClass:[Tile class]]);
self.scrollEnabled = ![result.superview isKindOfClass:[Tile class]];
return result;
}
Unfortunately this doesn't help and prints 0 in debugger.
The problem is, partly, because user interactions are disabled on the content view. However, enabling user interactions disables scrolling as the view captures all touches. So here is the solution. Enable user interactions in storyboard, but subclass the content view like so:
#interface LNContentView : UIView
#end
#implementation LNContentView
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView* result = [super hitTest:point withEvent:event];
return result == self ? nil : result;
}
#end
This way, hit test passes only if the accepting view is not self, the content view.
Here is my commit:
https://github.com/LeoNatan/ios-newbie
The reason Tile views don't get touches is that scroll view's pan gesture recogniser consumes the events. What you need is, attach a UIPanGestureRecongnizer to each of your tiles and configure them as follows:
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(pan:)]; // handle drag in pan:method
[tile addGestureRecognizer:pan];
UIPanGestureRecognizer *scrollPan = self.scrollView.panGestureRecognizer;
[scrollPan requireGestureRecognizerToFail:pan];
Here you let scroll view's pan gesture recogniser know that you only wish scrolling to happen if none of the tiles are bing dragged.
I've checked the approach — it does work indeed. Regarding your code, you'll need to handle all touches in the gesture recogniser rather than Tile view because touch events may be consumed/delayed by hit-tested view's gesture recogniser before they reach the view itself. Please refer to UIGestureRecognizer documentation to learn more about the topic.
It looks as ir one of the views in the hierarchy is capturing the events.
Have a look at the section
The Responder Chain Follows a Specific Delivery Path
Of the Apple doc's here
Edit:
Sorry I was writing from memory. This is how i resolved a similar issue in an app of myself:
I use UITapGestureRecognizer in the view(s) that I want to detect the touch. Implement the following delegate method of the UITapGestureRecognizer:
- (void) touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event
The touches' set contains all the objects (views) that received the event.
I'm building an iOS app that has a custom UIView upon a UIScrollView which in turn has a subview.
Here's the layout structure:
Note that the custom UIView(called "Detected Object Hint View") is not a subview of ScrollView, it's a sibling view of UIScrollView. And I want to respond to tap gesture on the custom UIView, so I've added UITapGestureRecognizer to the UIView, and it works for tap, but the UIScrollView will never get any touch events (not responding to scroll or zoom gesture).
I've googled a while, and a lot of people pointed out that in order for other view to respond to the touch events, I should implement the following method:
- (id)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
UIView *hitView = [super hitTest:point withEvent:event];
if (hitView == self){
return nil;
}
else {
return hitView;
}
}
But once I've added this method to my custom UIView, it will not respond to tap gesture either (of course).
So I'm wondering how can I handle the tap gesture on my custom UIView and pass the touch events to UIScrollView as well?
Big thanks!
I am trying to mimic a drag n drop of an image from a UITableView to a UIView. Both the UITableView and UIView are on the same view, side by side.
My approach:
is to fire the touchesBegan event inside the UITableView (i.e. when a cell is touched/selected) and fire the touchesEnded event when the touch ends in the UIView.
The problem:
So I created a custom UITableViewCell that contains a UIImageView and a couple of UILabels. And I set the User Interaction Enabled for this UIImageView. Then to receive the touchesBegan event, I did override the touchesBegan method inside the custom UITableViewCell class; and now I am able to receive the touchesBegan event from the cell.
The problem is
I am not getting the touchesEnded event when the touch ends on the UIView. Note that I already implemented the touchesEnded method inside the view controller class (not in the custom class); and if I start and stop a touch within this UIView, I see the touchesEnded being fired.
Is there anything I am missing here?
Using Xcode 4.6 (4H127), testing on iPad 2 with iOS 6.1.3
This will not work that way. You will need to utilize the touch methods on the parent view that contains both of your subviews. Could look abstractly like this:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if ([self touchOnCell:touches]) { //check to see if your touch is in the table
isDragging = YES; //view cell
}
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
//do your dragging code here
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
if (isDragging && [self touchOnDropView:touches]) {
//do your drop code here
}
}
now if this doesnt work like that alone you might want to implement the hitTest on the tableView and if the touch is in the dragging object's region, then return the parent view.
hope this helps
I'm trying to distinguish between horizontal swiping / panning and
vertical scrolling in a UITableView. The behavior I'm looking to
imitate (in a sense) is that of the Twitter iPad app, that has
multiple UITableView that can be moved horizontally on the screen. If
I slide my finger left or right on one of these UITableView, the view
itself moves horizontally. If I swipe vertically, the view scrolls as
expected.
I'm having trouble figuring out the correct way to implement this
behavior. I've seen some tutorials on this which involve adding touch
event handlers in the UITableViewCells, and overriding hitTest in the
UITableViewto appropriately route events depending on which direction
the gesture is moving. I've implemented some of these techniques, but
none of them work particularly well.
Does anyone know the correct way to implement this sort of behavior?
Conditionally performing actions on a UITableViewdependent on the
direction of the user's finger movement?
Thanks.
I've been struggling with a similar problem for days, and I've went through several potential solutions. I've found the best way and also the simplest solution to be subclassing UIGestureRecognizer to handle horizontal movement and attach it to your UITableViews.
The way it works is that it intercepts any touch events before they go to the UITableView (also UIScrollView). The UITableView, being a subclass of UIScrollView, has a custom UIPanGestureRecognizer built in which detects dragging and scrolls it's view accordingly. By adding your own subclass of UIGestureRecognizer, you can get the touches before the UIScrollView's gesture recognizer does. If your recognizer sees that the user is dragging horizontally, it should change it's state in an overridden touchesMoved: method to UIGestureRecognizerStateBegan. Otherwise, it sets it's state to UIGestureRecognizerCancelled, which lets the underlying UIScrollView handle the touches instead.
Here's what my UIGestureRecognizer subclass looks like:
#import <UIKit/UIGestureRecognizerSubclass.h>
#interface TableViewCellPanGestureRecognizer : UIGestureRecognizer
{
CGPoint startTouchPoint;
CGPoint currentTouchPoint;
BOOL isPanningHorizontally;
}
- (void)reset;
- (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;
#end
#implementation TableViewCellPanGestureRecognizer
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
startTouchPoint = [[touches anyObject] locationInView:nil];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesMoved:touches withEvent:event];
currentTouchPoint = [[touches anyObject] locationInView:nil];
if ( !isPanningHorizontally ) {
float touchSlope = fabsf((currentTouchPoint.y - startTouchPoint.y) / (currentTouchPoint.x - startTouchPoint.x));
if ( touchSlope < 1 ) {
self.state = UIGestureRecognizerStateBegan;
isPanningHorizontally = YES;
[self.view touchesCancelled:touches withEvent:event];
} else {
self.state = UIGestureRecognizerStateCancelled;
[self.view touchesCancelled:touches withEvent:event];
}
} else {
self.state = UIGestureRecognizerStateChanged;
}
}
-(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesCancelled:touches withEvent:event];
self.state = UIGestureRecognizerStateCancelled;
[self.view touchesCancelled:touches withEvent:event];
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];
self.state = UIGestureRecognizerStateCancelled;
}
-(void)reset
{
[super reset];
startTouchPoint = CGPointZero;
currentTouchPoint = CGPointZero;
isPanningHorizontally = NO;
}
#end
Then I have a subclassed UITableView that attaches the recognizer to itself and implements an action method to trigger horizontal movement of individual rows:
In my UITableView init:
horizontalPanGesture = [[TableViewCellPanGestureRecognizer alloc] initWithTarget:self action:#selector(handleHorizontalDrag:)];
[self addGestureRecognizer:horizontalPanGesture];
[horizontalPanGesture release];
And the action method:
-(void)handleHorizontalDrag:(UIGestureRecognizer *)gesture
{
UIGestureRecognizerState state = gesture.state;
// Set the touched cell
if (!touchedCell){
NSIndexPath *indexPathAtHitPoint = [self indexPathForRowAtPoint:[gesture locationInView:self]];
id cell = [self cellForRowAtIndexPath:indexPathAtHitPoint];
touchedCell = cell;
startTouchPoint = [gesture locationInView:touchedCell];
}
if ( state == UIGestureRecognizerStateBegan || state == UIGestureRecognizerStateChanged ) {
// move your views horizontally
} else if ( state == UIGestureRecognizerStateEnded || state == UIGestureRecognizerStateCancelled ) {
touchedCell = nil;
}
}
The above gets the current cell being touched within the table view, and then applies horizontal movements to it as the user drags left or right. However, if my gesture recognizer determines that the touches are meant to scroll vertically, it just cancels itself and the following touches are sent on to the UITableView to initiate vertical scrolling automatically.
This setup seems to be much simpler than overriding hitTest and doing all sorts of touch event trickery within the UITableView itself. It simply makes an immediate determination about the direction of the touch movement. You'll want to read up on UIGestureRecognizers - specifically about how it should be subclassed. You need to make sure to forward on certain touch events like touchesCancelled to the UITableView, as the UITableView's built in panGestureRecognizer won't be handling these events as it normally does. Obviously, you'll want to move entire table views and not individual cells, but it should be pretty straightforward.
This solution, as simple as it is, took me awhile to get exactly right for my exact needs. I am pretty new to IOS development, so I had to spend a lot of time reading about and tinkering with gesture recognizers and scroll views to figure this out.
I have never done this myself, but as a UITableView is a subclass of UIScrollView, the delegates of UITableView are also UIScrollViewDelegates. So in your UITableViewController subclass, you should be able to use UIScrollView delegates, and intercept the scrolls - making sure to also call the super method.
If you want the UITableViews to be placed "side by side" and when you swipe horizontally you expect them to all move together horizontally at the same time, (like a photo gallery with UITableViews instead of images) you can do the following:
Use a UIScrollView and add the UITableViews as the UIScrollView's subviews. You should set the scrollview's contentSize like this:
CGRect bounds = scrollView.bounds;
scrollView.contentSize=CGSizeMake(bounds.size.width * kNumOfTableViews, bounds.size.height);
so that the UIScrollview scrolls horizontally and not vertically.
You may also want to use
scrollView.pagingEnabled=YES;
depending on the desirable behaviour.
The UITableviews will respond the normal way if you slide your finger vertically and you will be able to change between UITableViews by sliding your finger horizontally.
For more details about how to do this efficiently, you can look at the WWDC 2010 video Session 104 - Designing Apps with Scroll Views and check out the source code from here: http://developer.apple.com/library/ios/#samplecode/PhotoScroller/ . This session describes how to slide between images. Instead of images you will use UITableViews
However, if you want each UITableView to be able to move horizontally independently and maybe overlap with another one as in the twitter app for iPad, this solution will not work for you, at least not out of the box.
I have some labels in my app like this...
What i need to do is, when clicking on a label, just I'm showing label name in the bottom of the screen. It works fine while clicking on each cell separately. But i want to show the changes even the user click on a particular label and move his finger on another label. That is, once he pressed on screen, where ever his finger moves, i want to trace those places and want to show the changes. How can i do this? Please explain briefly.
Thanks in Advance
By default, touch events are only sent to the view they started in. So the easiest way to do what you're trying to do would be to put all of your labels in a container view that intercepts the touch events, and let the container view decide how to handle the events.
First create a UIView subclass for the container and intercept the touch events by overriding hitTest:withEvent::
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
// intercept touches
if ([self pointInside:point withEvent:event]) {
return self;
}
return nil;
}
Set that custom class as the class of the container view. Then, implement the various touches*:withEvent: methods on your container view. In your case, something like this should work:
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
// determine which view is under the touch
UIView* view = [super hitTest:[[touches anyObject] locationInView:self] withEvent:nil];
// get that label's text and set it on the indicator label
if (view != nil && view != self) {
if ([view respondsToSelector:#selector(text)]) {
// update the text of the indicator label
[[self indicatorLabel] setText:[view text]];
}
}
}