I have a UIView that has touchesBegan:withEvent: to handle touches, this UIView is in the UITableViewCell.
When I touch it nothing happens, when I "long press" it then the touch event is trigerred.
Is there a setup I skipped? What can I do to have instant reaction to touch on my UIView?
This is the code:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint touchLocation = [touch locationInView:self];
[self handleTouchAtLocation:touchLocation];
}
When I put NSLog to this method, this is what is traced (formatting is mine):
touchesBegan:touches withEvent:<UITouchesEvent: 0x170078f00>
timestamp: 281850 touches: {(
<UITouch: 0x1781486c0>
phase: Began tap
count: 1 window: <UIWindow: 0x145e0ab30;
frame = (0 0; 320 568);
gestureRecognizers = <NSArray: 0x178240720>;
layer = <UIWindowLayer: 0x17802b800>>
view: <CSRateView: 0x145d49850;
frame = (50 54; 220 40);
autoresize = TM;
layer = <CALayer: 0x17003c0c0>>
location in window: {207, 81}
previous location in window: {207, 81}
location in view: {157, 7}
previous location in view: {157, 7}
)}
Disable delaysContentTouches for tableView
self.tableView.delaysContentTouches = NO;
Then, in you cell class:
#implementation MyCell
- (void)awakeFromNib
{
for (id view in self.subviews) {
if ([view respondsToSelector:#selector(setDelaysContentTouches:)]){
[view setDelaysContentTouches:NO];
}
}
}
#end
-- EDIT--
Or make a category on UITableViewCell
#import "UITableViewCell+DelayTouches.h"
#implementation UITableViewCell (DelayTouches)
- (void)setDelayContentTouches:(BOOL)enable
{
for (id view in self.subviews) {
if ([view respondsToSelector:#selector(setDelaysContentTouches:)]){
[view setDelaysContentTouches:enable];
}
}
}
#end
And in cellForRow:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = ...
[cell setDelayContentTouches:NO];
return cell;
}
Related
I have a tableview with 8 custom cells. in the 8th cell I added a scrollView with paging enabled so I can show page 1 and page 2 (or 3, 4... 10) without have a very high cell.
The problem is with the scrollView I can't use didSelectRowAtIndexPath because the cell is behind the scrollView so I'm trying to detect scrollView tap (not swipe).
I played with touchesBegan and touchesEnded but they are never called (I know touches work with UIView only, but maybe.....)
Any help is very appreciated.
Thanks,
Max
There is a trick Apple recommends to use in this case, in theirs WWDC 2014 session "Advanced scrollviews" (See Demo starting from 8:10):
[cell.contentView addSubview:_scrollView];
[_scrollView setUserInteractionEnabled:NO];
[cell.contentView addGestureRecognizer:_scrollView.panGestureRecognizer];
That's all what needs to be done, no need to override touchesBegan:, touchesMoved: and others.
I used solution based on overriding of touchesBegan:, touchesMoved:, touchesEnded: and touchesCancelled: previously, but sometimes it caused a weird behaviour: when select a certain cell, method -tableView:didSelectRowAtIndexPath: was called for cell with different indexPath.
Solution from Apple has no side effects so far and looks more elegant.
There is also an elegant resolution:
Create a SubClass from UIScrollView and override the following methods
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[[self superview]touchesBegan:touches withEvent:event];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
[[self superview]touchesMoved:touches withEvent:event];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
[[self superview]touchesCancelled:touches withEvent:event];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[[self superview]touchesEnded:touches withEvent:event];
}
Passing every touch to the superview of the scroll view and then the didSelectRowAtIndexPath will be called.
Solved subclassing both uitableviewcell and uiscrollview.
It worked for my needs. Hope it can help.
Max
myScrollView.h
#import <UIKit/UIKit.h>
#interface myScrollView : UIScrollView {
}
#end
myScrollView.m
#import "myScrollView.h"
#implementation myScrollView
- (id)initWithFrame:(CGRect)frame {
return [super initWithFrame:frame];
}
- (void) touchesEnded: (NSSet *) touches withEvent: (UIEvent *) event
{
NSLog(#"touch scroll");
// If not dragging, send event to next responder
if (!self.dragging)
[self.nextResponder touchesEnded: touches withEvent:event];
else
[super touchesEnded: touches withEvent: event];
}
myCell.h
#import <UIKit/UIKit.h>
#interface myCell : UITableViewCell {
}
#end
myCell.m
#import "myCell.h"
#implementation myCell
- (id)initWithFrame:(CGRect)frame {
return [super initWithFrame:frame];
}
- (void) touchesEnded: (NSSet *) touches withEvent: (UIEvent *) event
{
NSLog(#"touch cell");
// If not dragging, send event to next responder
[super touchesEnded: touches withEvent: event];
}
RootViewController.h
#import <UIKit/UIKit.h>
#class myCell;
#class myScrollView;
#interface RootViewController : UITableViewController {
myCell *cell;
myScrollView *scrollView;
}
#end
RootViewController.m
#pragma mark -
#pragma mark Table view data source
// Customize the number of sections in the table view.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
// Customize the number of rows in the table view.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 3;
}
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
// my custom cell
cell = [[myCell alloc] init];
if (cell == nil) {
cell = [[[myCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
// the custom scroll view
scrollView = [[myScrollView alloc] initWithFrame:cell.frame];
scrollView.contentSize = CGSizeMake(640, 40);
[cell.contentView addSubview:scrollView];
//something to add in scrollView
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 150, 20)];
label.text = #"some text";
[scrollView addSubview:label];
// Configure the cell.
return cell;
}
The selected answer is correct, but I updated the code based on a bug I was getting.
In the subclassed scroll view add the following code.
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
if (self.dragging) {
[super touchesMoved:touches withEvent:event];
} else {
if ([self.delegate isKindOfClass:[UITableViewCell class]]) {
[(UITableViewCell *)self.delegate touchesCancelled:touches withEvent:event];
}
[self.superview touchesMoved:touches withEvent:event];
}
}
If your self.delegate is not the UITableViewCell, than replace that property with a property to your cell.
The cell needs to retrieve the cancel touch event during movement to prevent the undesired results. It can be easily reproducible as follows.
Highlight the cell (assuming the scroll view is over the whole cell, if not highlight the scroll view)
While the cell is highlighted, drag the table view
Select any other cell and now the previously highlighted cell will retrieve the didSelectCell state
Another point to mention is that order matters! If the self.delegate is not called before the self.superview then the highlighted state wont happen.
I found the simplest solution for my needs:
subclass UIScrollView touchesEnded method and post a notification.
In the UITableview add an observer in viewdidAppear (remove it in viewdiddisappear) to call a function that call tableview didSelectRowForIndexPath.
Something like this (swift version)
// myScrollView.swift
import UIKit
class myScrollView: UIScrollView {
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
NSNotificationCenter.defaultCenter().postNotificationName("selectTVRow", object: nil)
}
}
In your tableView:
// ItemsList.swift
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "selectFourthRow", name: "selectTVRow", object: nil)
}
override func viewDidDisappear(animated: Bool) {
super.viewDidDisappear(animated)
NSNotificationCenter.defaultCenter().removeObserver(self, name: "selectfourthrow", object: nil)
}
func selectFourthRow() {
let rowToSelect:NSIndexPath = NSIndexPath(forRow: 4, inSection: 0);
self.tableView(self.tableView, didSelectRowAtIndexPath: rowToSelect);
}
/*
.... rest of your tableview Datasource and Delegate methods...
numberOfSectionsInTableView, numberOfRowsInSection, cellForRowAtIndexPath
*/
I am needing the CGPoint of a tap on my UITableViewCell. To do this I want to use the touchesBegan:withEvent: method in my subclassed UITableViewCell.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesBegan:touches withEvent:event];
UITouch *touch = [[event allTouches] anyObject];
CGPoint location = [touch locationInView:touch.view];
NSLog(#"%#", NSStringFromCGPoint(location));
}
How can I forward that from my UITableViewCell subclass to my current viewController so that I can make an action depending on where in the cell they tapped?
** I don't want to use a Gesture Recognizer on the cell since it will not allow the didSelectRowAtIndexPath: method to be called.
Either delegation or block call back would do it. Below is an example of using blocks
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
{
CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
__weak __typeof(self) weakSelf = self;
cell.touchedAtPoint = ^(UITableViewCell *cell, CGPoint point) {
[weakSelf doSomethingWithPoint:point);
};
return cell;
}
In the cell
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
{
[super touchesBegan:touches withEvent:event];
UITouch *touch = [[event allTouches] anyObject];
CGPoint location = [touch locationInView:touch.view];
void (^touchedAtPoint)(UITableViewCell *, CGPoint) = self.touchedAtPoint;
if (touchedAtPoint) {
touchedAtPoint(self, location);
}
}
- (void)prepareForReuse;
{
[super prepareForReuse];
self.touchedAtPoint = nil;
}
Declaration of ivar
#property (nonatomic, copy) void (^touchedAtPoint)(UITableViewCell *cell, CGPoint point);
Is there a way that I can get coordinates of clicked point inside collectionViewCell? I want to do method A if I clicked at rect with Y coordinate < 50, and method B if Y >50.
There is also option B, to subclass the UITableViewCell and get the location from the UIResponder class:
#interface CustomTableViewCell : UITableViewCell
#property (nonatomic) CGPoint clickedLocation;
#end
#implementation CustomTableViewCell
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesBegan:touches withEvent:event];
UITouch *touch = [touches anyObject];
self.clickedLocation = [touch locationInView:touch.view];
}
#end
Then get the location from the TableViewCell it self:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
//get the cell
CustomTableViewCell *cell = (CustomTableViewCell*)[tableView cellForRowAtIndexPath:indexPath];
//get where the user clicked
if (cell.clickedLocation.Y<50) {
//Method A
}
else {
//Method B
}
}
Assuming you have a custom UICollectionViewCell, you can add a UITapGestureRecognizer to the cell and get the touch point in the touchesBegan handler. Like this:
//add gesture recognizer to cell
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] init];
[cell addGestureRecognizer:singleTap];
//handler
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint touchPoint = [touch locationInView:self.view];
if (touchPoint.y <= 50) {
[self methodA];
}
else {
[self methodB];
}
}
I am trying to figure out how to dismiss the keyboard and trigger a method when the user taps outside of a UITextField
in TableViewCell.m:
-(void)textFieldDidBeginEditing:(UITextField *)textField
{
[self.delegate cellDidBeginEditing:self];
}
in ViewController.m:
-(void)cellDidBeginEditing:(TableViewCell *)editingCell
{
_editingOffset = _tableView.scrollView.contentOffset.y - editingCell.frame.origin.y;
for (TableViewCell *cell in [_tableView visibleCells]) {
[UIView animateWithDuration:0.3
animations:^{
cell.frame = CGRectOffset(cell.frame, 0, _editingOffset);
if (cell != editingCell) {
cell.alpha = 0.25;
}
}];
}
}
the cellDidBeginEditing: method displaces the cell to the top and shades the other cells grayish.
I have another method, cellDidEndEditing: which does the opposite of this, and the code is not really needed.
As of now, selecting, for example, "cell2" when editing "cell1" just triggers cellDidBeginEditing for "cell2"
I want the keyboard to dismiss and cellDidEndEditing to trigger when I click outside of "cell1"
Try implementing and calling these functions:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(#"touchesBegan:withEvent");
[self.view endEditing:YES];
[super touchesBegan:touches withEvent:event];
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
[self.firstTextField resignFirstResponder];
[self.secondTextField resignFirstResponder];
return YES;
}
This should make it so that when you touch any where outside of the two textFields, the keyboard automatically dismisses.
I am quite new to iOS development and trying hard to develop an App. I am unable to use swipe gestures in a multiview app. I am pasting the code, can you guys please point out the error.
It's a view based application.
Here is mainViewController.h
#import <UIKit/UIKit.h>
#interface mainViewController : UIViewController <UITableViewDelegate,UITableViewDataSource>
{
NSArray *listData;
}
#property (nonatomic, retain) NSArray *listData;
#end
Now mainViewController.m
#import "mainViewController.h"
#implementation mainViewController
#synthesize listData;
- (void)viewDidLoad {
NSArray *array =[[NSArray alloc] initWithObjects:#"Apple",#"Boy",#"Cat", nil];
self.listData = array;
[array release];
[super viewDidLoad];
}
- (void)dealloc
{
[listData release];
[super dealloc];
}
#pragma mark - View lifecycle
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section {
return [self.listData count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *SimpleTableIdentifier = #"SimpleTableIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:
SimpleTableIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc]
initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:SimpleTableIdentifier] autorelease];
}
NSUInteger row = [indexPath row];
cell.textLabel.text = [listData objectAtIndex:row];
//cell.textLabel.font = [UIFont boldSystemFontOfSize:50];
return cell;
}
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
NSUInteger row = [indexPath row];
NSString *rowValue = [listData objectAtIndex:row];
if([rowValue isEqualToString:#"Apple"])
{
cell.backgroundColor = [UIColor redColor];
}
else
if([rowValue isEqualToString:#"Boy"])
cell.backgroundColor= [UIColor yellowColor];
}
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSUInteger row = [indexPath row];
NSString *rowValue = [listData objectAtIndex:row];
if([rowValue isEqualToString:#"Apple"])
{
mainViewController* flashView=[[mainViewController alloc] initWithNibName:#"fl" bundle:[NSBundle mainBundle]];
[self.view addSubview:flashView.view];
}
//[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
- (void)viewDidUnload
{
[super viewDidUnload];
self.listData=nil;
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (CGFloat)tableView:(UITableView *)tableView
heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 110;
}
#end
I have added an UIViewSubController class with the xib file named "fl".
In fl.h:
#define kMinimumLength 5
#define kMaxVariance 1
#import <UIKit/UIKit.h>
#import "mainViewController.h"
#protocol flDelegate;
#interface fl : UIViewController <UIGestureRecognizerDelegate>
{
CGPoint gestureStartPoint;
id <flDelegate> delegate;
}
#property CGPoint gestureStartPoint;
#property (nonatomic,retain) id <flDelegate> delegate;
#end
#protocol flDelegate
-(IBAction)flDidFinish:(fl *)controller;
#end
And now in fl.m:
#import "fl.h"
#implementation fl
#synthesize delegate;
#synthesize gestureStartPoint;
- (void)dealloc
{
[super dealloc];
}
#pragma mark - View lifecycle
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch =[touches anyObject];
gestureStartPoint =[touch locationInView:self.view];
}
-(void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch =[touches anyObject];
CGPoint CurrentPosition =[touch locationInView:self.view];
if(CurrentPosition.x >= kMinimumLength)
{
NSLog(#"Go");
}
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
Here everything is working, but it is not detecting swipes, and not printing Go.
I also suggest taking a look at UISwipeGestureRecognizer, but if you prefer staying with your current design, this is the code that I am using elsewhere to detect swipes and pinch (zoom).
You will need to adapt it to your case (you have three methods instead of three branches).
if (touch.phase == UITouchPhaseBegan) {
// NSLog(#"TOUCH BEGAN");
_initialView = touchView;
startTouchPosition1 = [touch locationInView:self];
startTouchTime = touch.timestamp;
if ([allTouches count] > 1) {
startTouchPosition2 = [[[allTouches allObjects] objectAtIndex:1] locationInView:self];
previousTouchPosition1 = startTouchPosition1;
previousTouchPosition2 = startTouchPosition2;
}
}
if (touch.phase == UITouchPhaseMoved) {
// NSLog(#"TOUCH MOVED");
if ([allTouches count] > 1) {
CGPoint currentTouchPosition1 = [[[allTouches allObjects] objectAtIndex:0] locationInView:self];
CGPoint currentTouchPosition2 = [[[allTouches allObjects] objectAtIndex:1] locationInView:self];
CGFloat currentFingerDistance = CGPointDist(currentTouchPosition1, currentTouchPosition2);
CGFloat previousFingerDistance = CGPointDist(previousTouchPosition1, previousTouchPosition2);
if (fabs(currentFingerDistance - previousFingerDistance) > ZOOM_DRAG_MIN) {
NSNumber* movedDistance = [NSNumber numberWithFloat:currentFingerDistance - previousFingerDistance];
if (currentFingerDistance > previousFingerDistance) {
NSLog(#"zoom in");
[[NSNotificationCenter defaultCenter] postNotificationName:NOTIFICATION_ZOOM_IN object:movedDistance];
} else {
NSLog(#"zoom out");
[[NSNotificationCenter defaultCenter] postNotificationName:NOTIFICATION_ZOOM_OUT object:movedDistance];
}
}
}
}
if (touch.phase == UITouchPhaseEnded) {
CGPoint currentTouchPosition = [touch locationInView:self];
// Check if it's a swipe
if (fabsf(startTouchPosition1.x - currentTouchPosition.x) >= SWIPE_DRAG_HORIZ_MIN &&
// fabsf(startTouchPosition1.y - currentTouchPosition.y) <= SWIPE_DRAG_VERT_MAX &&
fabsf(startTouchPosition1.x - currentTouchPosition.x) > fabsf(startTouchPosition1.y - currentTouchPosition.y) &&
touch.timestamp - startTouchTime < 0.7
) {
// It appears to be a swipe.
if (startTouchPosition1.x < currentTouchPosition.x) {
NSLog(#"swipe right");
[[NSNotificationCenter defaultCenter] postNotificationName:NOTIFICATION_SWIPE_RIGHT object:touch];
} else {
NSLog(#"swipe left");
[[NSNotificationCenter defaultCenter] postNotificationName:NOTIFICATION_SWIPE_LEFT object:touch];
}
}
startTouchPosition1 = CGPointMake(-1, -1);
_initialView = nil;
}
EDIT: about your code...
you are not doing a proper swipe detection. indeed, take this line:
if(CurrentPosition.x >= kMinimumLength)
you are comparing a position (CurrentPosition.x) with a distance (kMinimumLength). This is not really meaningful. What you need saving the last touch position and then calculate the distance between the last touch and the current touch and compare this distance to kMinimumLenght.
The reason why I posted the sample code is that, by inspecting a working implementation you can understand what needs to be done in order to detect a swipe.
You should definitely take a look at UISwipeGestureRecognizers.