touchesBegan in custom UITableViewCell, touchesEnded in UIView - ios

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

Related

UIView handle and pass touch events to next sibling view

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!

Restrict one view panning at a time

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.

Touches method in UIViewController and UIView

I'm working on iPad app. My UIViewController contains only a custom UIView with a size of 500wx500h.
I implemented the touches methods both in the UIViewController and the custom UIView in order to call the UIViewController touches methods when we touch all around the custom UIView and call the custom UIView touches methods when we touch inside it.
UIViewController touchesMoved :
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint point = [touch locationInView:self.view];
NSLog(#"TEST"); // Added during edition
if (!CGRectContainsPoint(self.drawingView.frame, point)) {
NSLog(#"UIVIEWCONTROLLER");
}
}
Custom UIView touches Moved :
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"CUSTOM VIEW");
}
When I'm touching the custom UIView moving my finger, CUSTOM VIEW is logged until I removed the finger on the screen. On the other hand, when I'm touching outside of the custom UIView, the UIVIEWCONTROLLER is called only one time in the touchesMoved method.
Am I missing something wrong ?
EDIT : I added a log in my UIViewController touchesMoved method. When I touch inside the custom view, it logs the TEST during all the touching phase. But when I touch outside, I get the same behaviour.
add exclusiveTouch property to your view to disable multi touches
your view.exclusiveTouch=YES;
There is some likeliness that your self.view is intercepting touches and handling them, so they will not make it through to the view controller. You could try doing either of 2 things (or both) and see if it works:
self.view.exclusiveTouch = NO;
self.view.userInteractionEnabled = NO;
I would also try to call [super touchesMoved: touches withEvent: event];
Hope it helps.

Touch event handled by multiple views

I have a subclass of UIView on top of a UITableView. I am using the UITableView to display some data and, at the same time, I would like to overlay an animation that follows the finger (for instance, leaving a trail).
If I get it right, I need the touch events to be handled both by the UIView subclass and the UITableView. How can I do that?
Is it possible to have, ie, touchesMoved being triggered on the UIView subclass and then on UITableView?
Thank you so much for any help.
The way I have solved this problem is in a way that is not that clean, but it works. Please let me know if there's a better way to do this.
I have overridden hitTest for my custom UIView so that it directs touches to the UITableView underneath. Then in the UITableView I am handling the gestures through touchesBegan, touchesMoved, etc. There I am also calling touchesBegan on the UIView.
In this way touches are handled by two views.
The reason why I am not doing the other way around (having UIView's touchesBegan calling UITableView's touchesBegan) is that gestures recognizers on the UITableView would not work.
UIView subclass' hitTest
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
// tview is the UITableView subclass instance
CGPoint tViewHit = [tView convertPoint:point fromView:self];
if ([tView pointInside:tViewHit withEvent:event]) return tView;
return [super hitTest:point withEvent:event];
}
UITableView subclass's touchesBegan
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView:touch.view];
// ....
// view is the UIView's subclass instance
[view touchesBegan:touches withEvent:event];
}
No, you cann't do it implicity. Event Delivery chapter says
The window object uses hit-testing and the responder chain to find the
view to receive the touch event. In hit-testing, a window calls
hitTest:withEvent: on the top-most view of the view hierarchy; this
method proceeds by recursively calling pointInside:withEvent: on each
view in the view hierarchy that returns YES, proceeding down the
hierarchy until it finds the subview within whose bounds the touch
took place. That view becomes the hit-test view.
So, when window finds touched view it returns YES. Only one view can handle touches at the current moment.
But if you need to handle event for UITableView then handle it for UIView! You can convert touched point to required coordinates with – convertPoint, – convertRect functions, add subview to UITableView and move it depends on coordinate, and a lot of another things.
UITableView relays unhandled touch events to UIView. (Google "responder chain")
UITableView Documentation
So, you can handle your touch events in UIView only. So. In your UIView
touchesstart - do initialization stuff
touchesmove - draw tail on UIView (Use timers/delayedresponse to desable points so that it would look like a trail)
touchesend - do remaining stuff
Hope this helps.

Horizontal movement of a UITableView?

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.

Resources