Adding Gesture to TableViewCell - uitableview

I'm trying to add a swipe gesture recognizer to a tableViewCell but it doesn't work.
This is the way I create my cell:
CellIdentifier = #"EventsSentCell";
nibObjcet = [[NSBundle mainBundle] loadNibNamed:#"EventsSentCell" owner:self options:nil];
EventsSentCell *cell = [[EventsSentCell alloc] init];
cell = (EventsSentCell *)[nibObjcet objectAtIndex:0];
and this is how my cell is initiated in the .m file:
-(id)init{
self = [super init];
if (self) {
leftSwipe = [[UISwipeGestureRecognizer alloc] init];
leftSwipe.direction= UISwipeGestureRecognizerDirectionLeft;
[leftSwipe addTarget:self action:#selector(swipedLeft)];
[self addGestureRecognizer:leftSwipe];
}
return self;
}
and this is how I declared my gesture recognizer in the .h file:
#property (nonatomic,strong) IBOutlet UISwipeGestureRecognizer *leftSwipe;
But for some reason my method isn't called.
Any ideas?
Thanks
I've tried putting the below code:
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch{
NSLog(#"%#",gestureRecognizer);
return YES;
}
and the result i'm getting after swiping left is:
<UILongPressGestureRecognizer: 0xa9d99a0; state = Possible; view = <UITableViewCellContentView 0xa9d8ce0>; target= <(action=_longPressGestureRecognized:, target=<EventsSentCell 0xa9d8bb0>)>>

Before answering the actual question, let me point out some other issues in your code.
EventsSentCell *cell = [[EventsSentCell alloc] init];
cell = (EventsSentCell *)[nibObjcet objectAtIndex:0];
First of all, there's no point in these two lines. You're allocating and initializing an instance of EventSentCell without a nib. After doing this you're overwriting cell to point to the instance initialized by loadNibNamed:. You could simplify this to EventsSentCell = (EventsSentCell *)nibObject[0];
But even after these optimizations, this still isn't the recommended way to implement cellForRowAtIndexPath:. You should use registerNib:forCellReuseIdentifier: in viewDidLoad and then use
dequeueReusableCellWithIdentifier:forIndexPath: to get a cell and leave out loading the nib yourself completely.
Next,
#property (nonatomic,strong) IBOutlet UISwipeGestureRecognizer *leftSwipe;
You declare this property as an IBOutlet but you're setting it (as far as I know) only in code, more specifically the init method. You could just leave out the IBOutlet altogether.
And this init method is probably also the cause of your problem. When instantiating a view using loadNibNamed, initWithCoder: is called instead of init. Implement your custom initialization code (adding a gesture recognizer in this case) there and it should work just fine.

Your 'init' method is not getting called, so the gesture recognizer does not get set up.
You could try initialising in awakeFromNib instead, but anyway your cell creation looks unconventional.
Assuming you are using a custom cell class with a Xib file, here is how I would do it.
Create your EventsSentCell object .m and .h files
Create a xib file "EventsSentCell.xib"
In the xib file, delete the default top-level view and replace it with a UITableViewCell (you can drag one out from the objects library). In the identity inspector change it's class to EventsSentCell
In you table viewController's viewDidLoad...
UINib* EventsSentNib = [UINib nibWithNibName:#"EventsSentCell"
bundle:nil];
[self.tableView registerNib:EventsSentNib
forCellReuseIdentifier:#"EventsSentCell"];
In your cellForRowAtIndexPath method:
UITableViewCell *cell =
[tableView dequeueReusableCellWithIdentifier:#"EventsSentCell"];
if (cell == nil) {
cell = [[UITableViewCell alloc]
initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:#"EventsSentCell"];
}
In EventsSentCell.m, trigger your initialisation code from -awakeFromNib
Your initialisation code:
leftSwipe = [[UISwipeGestureRecognizer alloc] init];
leftSwipe.direction= UISwipeGestureRecognizerDirectionLeft;
[leftSwipe addTarget:self action:#selector(swipedLeft)];
[self addGestureRecognizer:leftSwipe];
will work as it is.
You get the UILongPressGestureRecognizer:... response to your gestureRecogniser delegate method because that is a built-in gesture recognizer provided by Apple that has it's delegate set to the cell. When your code is working correctly, if you also set your gesture recongnizer's delegate to the cell (leftSwipe.delegate = self), you would expect to see a simlilar log for UISwipeGestureRecognizer:...
It is also noting that the UILongPressGestureRecognizer's view is NOT the cell, but the cell's contentView. This is the superview for your cell's view hierarchy, to which you should attach all of your cell's content. Although your gesture recognizer works when you attach it to the cell, I would advise following Apple here:
[self.contentView addGestureRecognizer:leftSwipe];
Then the gesture will correctly follow the cell's view hierarchy.

Related

UITableView didSelectRowAtIndexPath not being called

I have a view controller with a UITableView. I have both the datasource and delegate set to the view controller. The method cellForRowAtIndexPath: is being called and behaving as expected however didSelectRowAtIndexPath: is not being called. Any ideas whats going on here?
I have this in my .h:
#interface DetailViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>
And in my viewDidLoad in the .m I have:
self.tableView.dataSource=self;
self.tableView.delegate=self;
It doesn't seem to make sense that the cellForRowAtIndexPath would be called but not didSelectRowAtIndexPath.This is what the method looks like for now:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
NSLog(#"did select row at index path");
}
I have encountered some possible reasons:
Make sure the the editing property of tableView is set to NO. The didSelectRowAtIndex: method isn't called when the editing property of table is set to YES. By default this property is set to NO.
self.tableView.editing = NO;
Make sure the allowsSelection property is set to YES. By default it is.
Try to call.[super viewDidLoad] in the viewDidLoad method or setting the delegate and the dataSource properties in the init initializer.
Set the delegate and the dataSource properties in the loadView method and assign the tableView to the view property of the UIViewController. Here's an example:
- (void)loadView
{
UITableView *tableView = [[UITableView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame] style:UITableViewStylePlain];
tableView.autoresizingMask = UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth;
tableView.delegate = self;
tableView.dataSource = self;
[tableView reloadData];
self.view = tableView;
}
(this is, actually, what I usually do) Try to subclass DetailViewController from UITableViewController which inherits from UIViewController too. UITableViewController comforms automatically to UITableViewDelegate and UITableViewDataSource.
Gesture recognizer on a view controller's view prevented touches, solved by setting gesture recognizer's cancelsTouchesInView to false.
In my case, I used UITapGestureRecognizer and it worked like a magic for me.
cell.tag=indexPath.row;
UITapGestureRecognizer* tap=[[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(tapOnCell:)];
[cell addGestureRecognizer:tap];
Here is my tapOnCell method implementation
-(void)tapOnCell:(UITapGestureRecognizer*)tap
{
ADPointCashTransaction* aTransaction=[transactionArray objectAtIndex:tap.view.tag];
// Work with aTransaction Object
}
Putting the answer in the comments of seto nugroho with my work around the problem. If you have a gesture recognizer in the tableView, then the didSelect row will not be called. What you can do is increase the number of taps for the gesture recognizer to two.
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
gestureRecognizer.numberOfTapsRequired = 2
view.addGestureRecognizer(gestureRecognizer)`
I'll add one more: make sure you don't have any other views overlapping the table or cells at any point, even hidden ones. If you do, disable 'User interaction enabled' in storyboard unless you explicitly need to catch gestures through the view.
In my case, I was assigning the dataSource and calling reloadData() all in a background thread. So, make sure you are doing this on the main thread.

UIButton with target:self not being sent to self?

I'm creating a new UIViewController (call it MyViewController), and adding the view (MyView) as a subview of a TableViewCell. Within MyView , there's a button. That button is created programmatically during MyViewController's init, as such:
-(id)initWithFrame:(CGRect)frame {
self = [super init];
if(self) {
[self.view setFrame:frame];
_yesButton = [[UIButton alloc] initWithFrame:CGRectMake(frame.size.width-150, 40, 140, 30)];
[_yesButton setTitle:#"Yeah!" forState:UIControlStateNormal];
[_yesButton addTarget:self action:#selector(didClick) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:_yesButton];
}
return self;
}
Now, seems straightforward. It displays properly, everything looks great in the simulator.
But when I click on the "_yesButton" within MyView, I get a crash with this error:
-[_UITableViewCellSeparatorView didClick]: unrecognized selector sent to instance 0x7b7f7ec0
What? When did "_UITableViewCellSeparatorView" come into the equation? I specifically told the _yesButton to set the Target to "self", so the selector should be sent to MyViewController, right? I could even imagine it getting tripped up and sending it to the UITableViewCell, since MyView is embedded within a TableViewCell, but why a SeperatorView?
Can anyone tell me how to get my _yesButton to send the call back to the MyViewController that it's being created within? And for bonus points, can you explain how "_UITableViewCellSeparatorView" became a thing in this conversation at all?
Edit: Here's how I'm building the cell in the TableView, and adding MyView to it. Note that I'm deliberately not using dequeuing for this row, although that might change if it's the source of the problem.
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"MyViewCell"];
MyViewController *myViewController = [[MyViewController alloc] initWithFrame:cell.contentView.frame];
[cell.contentView addSubview:myViewController.view];
return cell;
And the didClick method is currently empty, (it never even gets there, so I haven't gotten that far in writing it), but it's currently defined within MyViewController simply as:
-(void)didClick {
}
Solution #1
Actually this is because you MyViewController is not retain by ARC. The dealloc method id being called. Add ans instance of your controller in your UITableViewController will fix the issue and make ARC retains your controller.
Solution #2
Try something like this.
Create a custom UITableViewCell :
MyCustomCell.h :
#import <UIKit/UIKit.h>
#interface MyCustomCell : UITableViewCell
#property (strong, nonatomic) UIButton *yesButton;
-(id)initWithFrame:(CGRect)frame;
#end
MyCustomCell.m :
#import "MyCustomCell.h"
#implementation MyCustomCell
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
-(id)initWithFrame:(CGRect)frame {
self = [super init];
if(self) {
[self.view setFrame:frame];
_yesButton = [[UIButton alloc] initWithFrame:CGRectMake(frame)];
[_yesButton setTitle:#"Yeah!" forState:UIControlStateNormal];
[self.contentView addSubview:_yesButton];
}
return self;
}
#end
In your UITableViewControllerin the viewDidLoad function :
[self.tableView registerClass:[MyCustomCell class] forCellReuseIdentifier:CellIdentifier];
Then in your tableView:cellForRowAtIndexPath:
MyCustomCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifierr forIndexPath:indexPath];
if (cell == nil) {
cell = [[MyCustomCell alloc] initWithFrame:frame];
[cell.yesButton addTarget:self action:#selector(didClick) forControlEvents:UIControlEventTouchUpInside];
}
And now implement the following in your UITableViewController:
-(void)didClick {
// The following identify the in which cell the action has been triggered
NSSet *touches = [event allTouches];
UITouch *touch = [touches anyObject];
CGPoint currentTouchPosition = [touch locationInView:self.tableView];
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint: currentTouchPosition];
if (indexPath != nil)
{
// Do whatever you want for the given cell
}
}
I think that you're adopting a wrong approach.
The view cells are not living in memory like views. We may say that once the drawing done, it does not exists anymore.
For all cells operations, you should set you TableViewController as target, and handle the click method in this controller.
Only this controller has a global knowledge of the data in the cells and the cells formats.
You can set the tag of the button to the row index to know what row has been clicked.
your didClick selector is also not correct. It should have this form
-(void)didClick:(id)sender
{
NSInteger clickedRow = ((UIButton*)sender).tag
}
And so be connected like this:
[_yesButton addTarget:<YourTableController> action:#selector(didClick:) forControlEvents:UIControlEventTouchUpInside];
The problem is that the instance you thing to be kind of MyViewController, it is no. It is instead UITableViewCellSeparatorView.
The biggest error, is that you are trying to init a view controller by a method which is not supported from the class UIViewController, but is instead part of UIView:
- (id)initWithFrame:(CGRect)frame
I suggest you to review it before going ahead.

Custom TableViewCell not displaying label

I'm struggling with a problem I encountered while trying to create a custom UITableViewCell.
I subclassed UITableViewCell in SGTableViewCell and added it in the prototype cell in the storyboard.
As you can see the label is connected
and the cell identifier is set correctly
Then I linked the label to the SGTableViewCell.h like this
#property (strong, nonatomic) IBOutlet UILabel *nameLabel;
and in the .m file I have this code
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
self.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
[self addGestureRecognizer:recognizer];
self.nameLabel = [[UILabel alloc] init];
_checkView = [[UIView alloc] initWithFrame:CGRectNull];
_checkView.backgroundColor = kGreen;
_checkView.alpha = 0.0;
[self addSubview:_checkView];
self.nameLabel.text = #"Hello";
}
return self;
}
But when I use this cell in my tableview using this code
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
Episode *episode = [self.selectedSeason.episodeList objectAtIndex:indexPath.row];
SGTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Episode"];
UIView *selectionColor = [[UIView alloc] init];
selectionColor.backgroundColor = kSelectionGrey;
cell.selectedBackgroundView = selectionColor;
cell.backgroundColor = kBackgroundGrey;
cell.nameLabel.text = episode.name;
NSLog(#"%#", cell.nameLabel.text);
cell.nameLabel.textColor = [UIColor redColor];
return cell;
}
I get no text at all.
I tried logging the text from each label in each cell and it gives me the right text.
I tried setting programmatically a different disclosure indicator for the custom cell and it did change so everything is allocated and working but label is not displaying.
I honestly have no idea of what's the problem. Did I miss something?
Thank you
PARTIALLY SOLVED:
OK i tried doing the same thing on an empty project and everything worked flawlessly so I checked again my project and found this line
[self.tableView registerClass:[SGTableViewCell class] forCellReuseIdentifier:#"Episode"];
Seeing it was not necessary for the empty project i commented this line and everything started working.
The only problem i have now is that if i don't use this line i can't use the custom cell as was intended. In fact my custom cell is swipable using a pan gesture recognizer but without registering my custom class to the tableview seems like the swipe doesn't work.
Sorry for the trouble, seems like i messed up again :/
You shouldn't alloc init a label that you created in the storyboard, it is already allocated automatically. When you do self.nameLabel = [[UILabel alloc] init];, you reset the self.nameLabel property to point to a new empty memory location and not to the label created in the storyboard, hence you can change its text property and see the result in NSLog but not in the storyboard because it doesn't refer to that label in the storyboard.
Try removing all initialisation from the initWithStyle method (to make sure nothing is covering it such as that subview you create), and everything related to the label in the cellForRowAtIndexPath method (same reason), and try a simple assignment like self.nameLabel.text = #"Test text" in the cellForRowAtIndexPath method, it should work. Then add all your other initialisation.
And yeah, don't forget to input your cell reuse identifier "Episode" in the storyboard.
Make sure you:
Have linked the delegate and the datasource to the view the tablview is housed in.
Have that view implement UITableViewController and UITableViewDelegate (I'm pretty sure it is both of those).
Implement the necessary methods, which you seem to have done. You need the row size, section size, and the add cell methods
After updating the array linked to your tableview, call [tableView reloadData]
Have a look at this link:Tutorial to create a simple tableview app

TableViewCell image change when touched

I have my own TableViewCell class which inherit UITableViewCell. On my cell nib file, I have put a image in my TableViewCell. (The image does not fully occupy the whole cell area, there are spaces around image)
Then, I would like to implement a touch feedback feature that when user touch the image in the cell, the image will be replaced by another image.
I tried to do it in tableView:didSelectRowAtIndexPath: method :
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//my TableViewCell
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
//New image to replace the current one
UIImage* bg = [CCTheme imageNamed:#"green_btn_bg"];
UIImageView* bgView = [[UIImageView alloc] initWithImage:bg];
cell.selectedBackgroundView = bgView;
...
But it does not work at all. So, how can I implement this touch feedback feature?? That's when user finger touched the image in cell, the image get changed to another one.
Create a UIImageView property in your custom UITableViewCell class as an IBOutlet then set the image on that property from the cellForRowAtIndexPath: rather than the background property.
cell.yourCustomImageView.image = bgView;
Or add the UIImageView to the current generic UITableViewCell like below.
with your Current cell
[cell addSubview:bgView];
In didSelectRowAtIndexPath: you have to first change your data model (indicate that the status of the cell has changed).
You set the appropriate image cellForRowAtIndexPath:. (I.e. you check what the status is and provide the correct image.)
To update the cell, call reloadRowsAtIndexPaths:.
Explanation
You should not store the state of your cell in some view of the cell. E.g. if the different image represents some kind of selection, your array or other data structure that feeds your table view should keep track of which row is in this state.
Each time the cell has to be regenerated, cellForRowAtIndexPath is called. (This could be because the cell becomes visible, or because you explicitly update it.) This is the best place to check for the state information and display the cell accordingly.
I would recommend hooking a gesture recognizer to the UIImageView within your custom cell's init method:
// Add recognizer for tappable image
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc]
initWithTarget:self action:#selector(imageViewTapped:)];
[tapRecognizer setNumberOfTouchesRequired:1];
[tapRecognizer setDelegate:self];
self.tappableImageView.userInteractionEnabled = YES;
[self.tappableImageView addGestureRecognizer:tapRecognizer];
Then the handler:
- (void)imageViewTapped:(id)sender {
NSLog(#"image tapped");
self.tappableImageView.image = [UIImage imageNamed:#"some_different_image.png"];
}
Also, don't forget to have you custom cell declaration decorated like so:
#interface CustomTableViewCell : UITableViewCell <UIGestureRecognizerDelegate>
In the cellForRowAtIndexPath: method you need to ensure that the code in your init method is being fired so that the tapRecognizer is added.
Good luck!
[EDIT]
Depending on how you create your cell with the custom XIB you may not need this, but in my case I needed to explicitly call a method to initialize the state of the UI in the table cell. The way I do this is offer an initState method on the custom table view cell:
- (void)initState {
// Do other things for state of the UI in table cell.
// Add recognizer for tappable image
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc]
initWithTarget:self action:#selector(imageViewTapped:)];
[tapRecognizer setNumberOfTouchesRequired:1];
[tapRecognizer setDelegate:self];
self.tappableImageView.userInteractionEnabled = YES;
[self.tappableImageView addGestureRecognizer:tapRecognizer];
}
Then in cellForRowAtIndexPath I make sure to call initState on my table cell after it has been created:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
CustomTableViewCell *cell = (CustomTableViewCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
UIViewController *temporaryController = [[UIViewController alloc] initWithNibName:#"CustomTableViewCell" bundle:nil];
// Grab a pointer to the custom cell.
cell = (CustomTableViewCell *)temporaryController.view;
[cell initState];
}
return cell;
}

iOS - Adding Target/Action for UITextField Inside Custom UITableViewCell

I have UITableView which uses a custom UITableViewCell containing a UITextField.
I want to call a method (myMethod) when it is clicked (UIControlEventTouchDown) and attempted to wire this up inside the UITableView delegate method cellForRowAtIndexPath by doing the following:
[tf addTarget:self action:#selector(myMethod) forControlEvents:UIControlEventTouchDown];
When the UITextField is clicked, nothing happens.
I attempted to do the exact same thing for another UITextField outside of the UITableView:
[othertf addTarget:self action:#selector(myMethod) forControlEvents:UIControlEventTouchDown];
When I click on othertf the method is called as I would expect.
I'm a bit confused as the code is identical apart from I've swapped tf for othertf.
Below is the complete code for cellForRowAtIndexPath:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *DetailCellIdentifier = #"DetailFieldView";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:DetailCellIdentifier];
if (cell == nil) {
NSArray *cellObjects = [[NSBundle mainBundle] loadNibNamed:DetailCellIdentifier owner:self options:nil];
cell = (UITableViewCell*) [cellObjects objectAtIndex:0];
}
cell.selectionStyle = UITableViewCellSelectionStyleNone;
UITextField *tf = (UITextField *)[cell viewWithTag:2];
tf.text = #"some value";
[othertf addTarget:self action:#selector(myMethod) forControlEvents:UIControlEventTouchDown];
[tf addTarget:self action:#selector(myMethod) forControlEvents:UIControlEventTouchDown];
return cell;
}
Can anyone spot what I'm doing wrong? It's probably something simple as I am new to iOS development.
Use UITextField delegate Methods:
UITextField delegate
//Use this method insted of addTarget:
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
if (textField == tf) {
[self myMethod];
return NO;
}
return YES;
}
and dont forget to set the delegate to the textField:
tf.delegate = self;
When you touch a UITextField it swallows the touch itself and tells the keyboard to appear etc. Instead use the following events:
UIControlEventEditingDidBegin
UIControlEventEditingDidEnd
UIControlEventEditingChanged
Further improvements
Tags is a rather loose way of coupling your NIB and your code. Consider Making a DetailCell.h and DetailCell.m file, and set the root view in the NIB file to the DetailCell class, and create reference outlets for all the views you need to access in code, and action outlets for all the actions you need to react on.
This is done by CTRL clicking on a view in the interface builder, and dragging it into the DetailCell.h file. Interface Builder will now ask you wheter you want to create an action or a reference outlet (References are basically pointers, and actions are basically events)
dequeueReusableCellWithReuseIdentifier now calls initWithFrame on your DetailCell class every time is creates a new instance of it. It is now your job to load the NIB file in this function.
Alternatively you could register a NIB file for cell reuse on the tableview with
[tableview registerNib:[UINib nibWithNibName:DetailCellIdentifier
bundle:nil] forCellWithReuseIdentifier: DetailCellIdentifier];
which the automatically loads the nib file every time a new cell is created. In >= iOS 5 (AFAIK) dequeueReusableCellWithReuseIdentifier never returns nil, but creates instances of the registered classes/nib files itself every time.

Resources