I have a UITableView as a subview of my UIScrollVIew, which is the main view controlled by my MainViewController.
In MainViewController.h
#interface MainViewController : UIViewController <UIGestureRecognizerDelegate, UITableViewDelegate, UITableViewDataSource>
// other stuff here...
#property (weak, nonatomic) IBOutlet UITableView *myTableView;
In MainViewController.m
#synthesize myTableView;
// other stuff here...
- (void)viewDidLoad {
myTableView.delegate = self;
myTableView.datasource = self;
}
// other stuff here...
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath: (NSIndexPath *)indexPath {
[self performSegueWithIdentifier:#"listAttributesSegue" sender:self];
}
I know that didSelectRowAtIndexPath is not being called because I have set breakpoints on both the method itself and the line of code inside it, and neither is being called. I also know that the datasource is working correctly because I have other functions which modify the cells at runtime and they are working perfectly fine. I am using the latest Xcode with iOS 5.0 set as the development target. I have searched and searched for an answer. Anyone have any ideas?
Edit:
I have found the answer. I had a UITapGestureRecognizer set for myTableView's superView. This overrode the selection call. Credit to whoever suggested that that might be it. Your answer was deleted before I could mark it correct.
Edit 2:
A lot of people have been commenting about this, so I though I would share it. If you are experiencing this problem, simply set myGestureRecognizer.cancelsTouchInView to false and everything should work fine.
I have found the answer. I had a UITapGestureRecognizer set for myTableView's superView. This overrode the selection call. Credit to whoever suggested that that might be it. Your answer was deleted before I could mark it correct.
Set the cancelsTouchesInView property to NO on the gesture recogniser to allow the table view to intercept the event.
Updated for Swift 3:
if you are used UITapGestureRecognizer in your code :- # Swift 3
use below lines of code:
extension YourViewController{
func hideKeyboardWhenTappedAround() {
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(YourViewController.dismissKeyboard))
view.addGestureRecognizer(tap)
tap.cancelsTouchesInView = false
}
func dismissKeyboard() {
view.endEditing(true)
}
}
How to called:-
In ViewDidLoad()
self.hideKeyboardWhenTappedAround()
Your problem is case-sensitivity. Your code:
- (void)tableVIew:(UITableView *)tableView didSelectRowAtIndexPath: (NSIndexPath *)indexPath {
should be
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath: (NSIndexPath *)indexPath {
Have you defined instance variable for tableview with same name.
If not then might be this can be the issue-
_myTableView.delegate = self;
_myTableView.datasource = self;
Or-
self.myTableView.delegate = self;
self.myTableView.datasource = self;
Maybe it is a typo after all. Check that your function is not didDeselectRowAtIndexPath: (de select instead of select).
My solution is :
set cancelsTouchesInView To No of any tapGesture
I found in my custom cell , userInteractionEnable is set to
NO, simply delete userInteractionEnable = No and issue solved.
Cancel the other views touches except required one.
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gesture shouldReceiveTouch:(UITouch *)touch {
if (touch.view == your view) {
return YES;
}
return NO;
}
Sorry, haven't got enough points to add comments - Garret's answer is great but I would add:
You can still have your gesture recognizer but you will need to set 'Cancels touches in view' to NO - then the gestures will be handed on to the view and your UITableView will work fine.
After trying many, many approaches this seems to be the correct way of doing things: a tap gesture recognizer with 'cancel touches in view' is like having an invisible layer on top of everything that grabs all the events and routes them to the view controller (the proxy). The view controller then looks at the gesture to see if it has an action binding (buttons etc.) and will route those and any remaining will just go to the gesture handler. When using a UITableView it is expecting to receive the tap but the view controller snaffles it when you have 'Cancels touches in view'.
I was having this issue for a while, and I did not see any reference to it here, so for reference, another reason for this could be that:
tableView.editing = YES;
but
tableView.allowsSelectionDuringEditing = NO;
As per documentation for
- tableView:didSelectRowAtIndexPath:
This method isn’t called when the editing property of the table is set to YES (that is, the table view is in editing mode). See "Managing Selections" in Table View Programming Guide for iOS for further information (and code examples) related to this method.
My case is strange. My tableView has 2 sections. 1st section's cells work fine about tableView:didSelectRowAt:, but 2nd section's cells doesn't trigger didSelectRowAt:.
The above problem happens at iPhone 4s, iOS 9.3. But in iPhone 5s, iOS 10.3, there are no problems, those cells works fine. It seems like iOS 9 bugs about UITableView.
After many tests, I found out one line codes produces this bug.
tableView.estimatedSectionHeaderHeight = 60.0
Because the 2nd sections has no header view. I remove this line, and all works fine.
A cell can be selected by the user (tapping on the row), by calling "tableView.selectRowAtIndexPath(..)" or "cell.setSelected(true, ...).
If the cell is selected by calling "cell.setSelected(true)", the user
cannot deselect the cell anymore.
If the cell is selected by calling
"tableView.selectRowAtIndexPath()", the user can deselect the cell as
expected.
I had intermittent failure of didSelectRowAtIndexPath: being called on my custom cell press.
I discovered that if I stopped calling [tableView reloadData] very often (10 Hz), and changed it to update every 2 seconds, almost every press would successfully call didSelectRowAtIndexPath:
It seems like reloading the view blocks presses.
My problem is the cell is a customized cell, and the action does not work on it. In addition, there is a UITapGestureRecognizer defined in the superclass.
Firstly,
Set tapGesture.cancelsTouchesInView = false
override func viewDidLoad() {
super.viewDidLoad()
initUI()
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(endEditing))
tapGesture.cancelsTouchesInView = false
view.addGestureRecognizer(tapGesture)
}
Secondly, Instead of setting
isUserInteractionEnabled = true; in the table view, I set the action on the cell.
In the ViewDidLoad()
super.viewDidLoad()
tableView.delegate = self
}
Then in the
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell: UITableView = tableView.dequeueReusableCell(for: indexPath)
cell.isUserInteractionEnabled = true;
You can try this solution if you are creating a customized cell.
It's work for me, can you try!
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.dismissKeyboard))
tap.cancelsTouchesInView = false
view.addGestureRecognizer(tap)
Related
I have a collection view, the datasource delegate works well, but UICollectionViewDelegate:
-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"didselect");
}
not get called, although i set the delegate (as i did with data source and it worked)
I have to mention that my cell is loaded from a nib and is connected to a subclass of UICollectionViewCell, anyway the cells do not respond to my touch. I enabled the user interaction in the UIImageView that is in my cell.
also :
-(BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"this is caled");
return YES;
}
is not getting called!
as I mentioned I did set:
[self.collectionView setDelegate:self];
and of course
<UICollectionViewDelegate>
also I don't have any touchBegan override ..
UPDATE:
WEIRD! it only gets called if I long press! how can I fix this, I set delaysContentTouches to NO plus i don`t have any gesture recognizers implemented.
help please. thanks.
It looks like there is a UITapGestureRecognizer somewhere up in the view hierarchy. By default, UITapGestureRecognizers consume the touch that they recieve, meaning that it is not passed to the views below it in the hierarchy. You need to find the rogue tap gesture and add this line
tapGestureRecognizer.cancelsTouchesInView = NO;
This will make it pass touches to views below it in the hierarchy, and hopefully solve your problem.
Looks like you've added TapGestureRecognizer somewhere and it prevents selecton of cell. Check them, that should be the problem.
I was facing the same issue, that clicking on the custom UICollectionView Cell, it was not detecting the click.
In my case, the problem was that in the CustomCell's Xib, the userInteraction was enabled and that's why UICollectionview's didSelectItemAtIndexPath was not getting called, instead user tap information was being sent to that particular cell for which I had no handler.
I had a similar issue with PSUICollectionView (this works on iOS5 too) and I fixed it by putting a button on my CollectionViewCell and setting the target of that button
Also add tag's to know which button is pressed.
In my case I had TapRecognizer added in self.view due to which all taps in Screen is recieved at self.view not in collectionViewDidSelect.
So take care of this .
in ur .h file, import CellViewController and add delegate
#import "myColleCell.h"
UIViewController<UICollectionViewDataSource,UICollectionViewDelegate,UICollectionViewDelegateFlowLayout>
in ur .m file,add the following codes to ur ViewDidLoad,
UINib *cellNib = [UINib nibWithNibName:#"myColleCell" bundle:nil];
[myCollectionView registerNib:cellNib forCellWithReuseIdentifier:#"myColleCell"];
UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init];
[flowLayout setItemSize:CGSizeMake(220, 220)];
[flowLayout setScrollDirection:UICollectionViewScrollDirectionHorizontal];
[myCollectionView setCollectionViewLayout:flowLayout];
and setup cell with ur CellViewController
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *identifier= #"myColleCell";
myColleCell *cell = (myColleCell *)[collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
[cell setupCell:[dataArray objectAtIndex:indexPath.row]];
//setup cell function methods placed in your CellViewController
return cell;
}
and finally make sure that your cellView, collectionView are set user interactive to YES
Ensure there aren't any objects setting the userInteractionEnabled property to NO on the UICollectionViewController.
Similar to what other people are saying, I had this same problem and it was fixed by removing a call to userInteractionEnabled where the parent view was adding it as a child view controller. I was able to test this by adding a UIButton to the cell and determining that even it couldn't receive the touch events.
Adding here as a reference for other people who are looking for the answer
Short Answer:
Delay the touches of default gesture recognizers associated with the tableview:
if let gestures = tableView.gestureRecognizers{
for gesture in gestures {
gesture.delaysTouchesBegan = true
}
}
Explanation
Every tableview has gesture recognizers associated with it. Which causes the delays of touches to custom UItableView cell. Set the delaysTouchesBegan to true so that the touch can be passed to subviews quickly.
In my case it was CollectionViewController inside UItableViewCell for which collectionView:didSelectItemAtIndexPath was being called with a delay.
Maybe you should use a tap gesture on the collection view.
I am trying to setup a UITableViewController so that its tableView belongs to my custom subclass. My loadView method currently looks like this:
- (void) loadView {
MyTableViewSubclass* tv = [[[MyTableViewSubclass alloc]initWithFrame: CGRectZero style: UITableViewStylePlain]autorelease];
self.view = tv;
self.tableView = tv;
}
I am getting crashes later on that go away if I comment out the above method. So something is missing. But what?
Apple's documentation says I should not be calling super in loadView. Which makes sense, because I want the view to have my class, not theirs.
Things I've tried that don't help:
Rewrite the loadView method so that it is creating a plain UITableView. This tells me that the source of the problem does not lie in the implementation of my subclass.
Call [super viewDidLoad] from my loadView method. From Apple's docs, it is unclear to me whether that method is called from loadView or afterwards. In any case, adding it to the end of my loadView method does not help.
One thing I've tried that does fix the problem, but defeats the purpose:
Comment out my loadView method.
EDIT: the crash is shown below. It happens after the user does some input. It also happens the same way if I am creating a plain UITableView instead of my subclass. There is a lot going on in the app, and something in my loadView override [or more likely, something missing from my override] is causing the state to be different, which in turn leads to the crash. But I don't see a good way to track what is different.
2011-09-08 12:44:59.591 MyAppName[97649:207] *** Terminating app due to uncaught exception 'NSRangeException', reason: '-[MyTableViewSubclass scrollToRowAtIndexPath:atScrollPosition:animated:]: row (0) beyond bounds (0) for section (0).'
Turns out I need to set the dataSource and delegate of my tableView as a part of loading. So when I do this, everything works fine:
- (void) loadView {
MyTableViewSubclass* tv = [[[MyTableViewSubclass alloc]initWithFrame: CGRectZero style: UITableViewStylePlain]autorelease];
tv.dataSource = self;
tv.delegate = self;
self.view = tv;
self.tableView = tv;
}
William's answer help me over the last hurdle too.
To add a swift example, the pattern I commonly use is:
class SomeTableViewController: UITableViewController {
private var childView: SomeTableView! { return tableView as! SomeTableView }
override func loadView() {
tableView = SomeTableView(frame: UIScreen.mainScreen().bounds, style: .Plain)
tableView.delegate = self
tableView.dataSource = self
view = tableView
}
}
You are then free to refer to the customised view as childView in other places in the ViewController.
When I push a view after a user has selected a UITableView row, the row gets a blue highlight, and then the new view appears. That's fine. But when I go 'back' the row is still highlighted in blue. Here's my didSelectRowAtIndexPath code.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
SettingsViewController *controller = [[SettingsViewController alloc] initWithNibName:#"SettingsView" bundle:nil];
[[self navigationController] pushViewController:controller animated:YES];
[controller release], controller = nil;
}
What am I doing wrong?
As the answers above point out, you need to explicitly deselect the row. You have two options as to how you do this. The first, is to deselect the row immediately after selection:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
...
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
That will work just fine, but there is an alternative, and its the approach taken by UITableViewController which is to leave the row selected then deselect it when the view re-appears (after the controller you're pushing is popped off of the stack).
This has the slight advantage that the user sees a glimpse of their previous selection when they return so they can see what they had selected previously.
To implement this, you just need to override viewWillAppear:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:YES];
}
As I said, this is what the default of implementation of UITableViewController's viewWillAppear: does so if you are using UITableViewController and not seeing this behaviour, you should check that you are calling the super implementation in your class' own viewDidAppear:.
Update (30 Oct 2013): well, this is a popular answer! As Ben rightly points out in the comments, UITableViewController actually does this in viewWillAppear: not viewDidAppear: - this is the correct timing. In addition, you turn this behaviour on and off using the clearsSelectionOnViewWillAppear property of UITableViewController. I've amended my answer above to reflect this.
UITableViewController has a BOOL property called clearsSelectionOnViewWillAppear which does exactly what you want.
By default it is set to YES, but I've noticed that you can (sometimes accidentally) disable this property by implementing your own viewWillAppear: method. I think this is because the deselection happens during [UITableViewController viewWillAppear:] which might never get called if you override it.
The solution is easy, then. Just call super's version of viewWillAppear: somewhere in your version of that method:
- (void)viewWillAppear:(BOOL)animated {
// Your custom code.
[super viewWillAppear:animated];
}
Apple recommends always calling the super version of any of the view{Did,Will}{A,Disa}ppear methods if you override them.
References
UITableViewController Class Reference (for clearsSelectionOnViewWillAppear)
UIViewController Class Reference (for viewWillAppear:)
You need to deselect it:
[tableView deselectRowAtIndexPath:indexPath animated:YES];
If your controller is based on UITableViewController, you get this feature for free. But I often ended up using UIViewController with no. of other controls in addition to UITableView, in that case, you should override your viewWillAppear to
-(void) viewWillAppear:(BOOL)animated{
// Unselect the selected row if any
NSIndexPath* selection = [devListTableview indexPathForSelectedRow];
if (selection){
[tableview deselectRowAtIndexPath:selection animated:YES];
}
}
You just need to call [tableView deselectRowAtIndexPath: indexPath animated: YES].
The default behaviour of UITableViewController deselects the row when the user returns from the detail view. The response given by Luke is fine, but I want to point out the reason for it:
1- If you have your UITableViewController like it was when you created it from scratch, you will have all the default behaviours.
2- If in the situation 1 you add a -viewWillAppear or -viewDidAppear, then you will be overwriting the standard behaviour. Them, if you want that the row deselects on return, you must say the super explicitly that you'd like it to deselect the row by himself as it always did! To achieve this, as Luke says, you must call [super viewWillAppear:animated] or [super viewDidAppear:animated] like this:
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// Here goes all of your stuff
}
This solution is for swift
To solve this problem just add tableView.deselectRow(at: indexPath, animated: false) in didSelectRowAt indexPath function .
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
Watch this 1 minute video tutorial to visualize .
Another solution is to call UITableView's reloadData in viewWillAppear
I am new to iphone development.I created a table displaying my contents.If i select a row ,it state is highlighted in blue color and navigates to another view and if i click the back button it navigates back to the table showing the clicked cell in blue color,i want to remove the highlighted color on table while navigating back to its view.How can i do that. Thanks.
I think the generally accepted way to do this is to deselect the cell as you're navigating to the new view. Instead of viewWillAppear, use the tableview delegate method didSelectRowAtIndexPath and the same deselectRowAtIndexPath you were using.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)newIndexPath{
[tableView deselectRowAtIndexPath:newIndexPath animated:YES];
}
(and by generally accepted, I mean "stuff I most often see in example code". It depends on what you want it to look like in the end)
I finally got it by implementing this in my table view class.
- (void)viewWillAppear:(BOOL)animated
{
NSIndexPath *tableSelection = [self.tableView indexPathForSelectedRow];
[self.tableView deselectRowAtIndexPath:tableSelection animated:NO];
}
You are doing it wrong, on cellForRowAtIndexPath method. use this
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
On your UITableViewCells
This would be the correct way if you wanted the cell not to be highlighted nor on selection touch neither on coming back to view. If you only want the cell deselected when coming back to view the other solutions are more suitable.
The default implementation of viewDidAppear: should take care of that for you. If you did override that method in your table view controller, don't forget to call [super viewDidAppear:animated] in your override method.
For Swift 3:
override func viewWillDisappear(_ animated: Bool) {
if let indexPath = self.tableview.indexPathForSelectedRow {
tableview.deselectRow(at: indexPath, animated: true)
}
}
There is a similar question to this but answer is very general, vague.( Detecting UITableView scrolling )
Please don't dismiss. I am looking for concrete solution.
I have UITableView which has editable textfield and PickerView appears when another cell selected.
What I need is to hide firstResponder or PickerView when user starts scrolling this UITableView.
So far in question Detecting UITableView scrolling there's a sugestion that you should subclass UITableView. IF you subsclass UITableView still internal/private UIScrollView is not accessible.
How do I access UITableView's parent ScrollView (without breaking the law)?
Thanks.
You don't need to subclass UITableView to track scrolling. Your UITableViewDelegate can serve as UIScrollViewDelegate as well. So in your delegate class you can implement -scrollViewWillBeginDragging: or whatever UIScrollViewDelegate method you need in your situation. (as actually suggested in the question you mention)
To expand on Vladimir's answer, this is how I implemented this solution:
In .h file:
#interface MyViewController : UIViewController <UIScrollViewDelegate>
In .m file:
- (void)scrollViewWillBeginDragging:(UIScrollView *)activeScrollView {
//logic here
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
if (scrollView == myTableView){
// Your code here.....
}
}
I had the same problem, and I got some ideas from the answers above to fix it, but not only the app crashes if I want to refresh while the table view is being scrolled, but also it crashes if I scroll while it's being refreshed. So the extended solution to fix the problem under all circumstances is to:
1.1. Disable scrolling if the user has pressed the refresh button
1.2. Enable scrolling once the refresh process is done
2.1. Disable the refresh button if the user is scrolling
2.2. Enable the refresh button once the user is finished scrolling
To implement the first part (1.1., and 1.2.):
-(void)startReloading:(id)sender
{
...
self.tableView.userInteractionEnabled = NO;
// and the rest of the method implementation
}
-(void)stopReloading:(id)sender
{
self.tableView.userInteractionEnabled = YES;
// and the rest of the method implementation
}
To implement the second part (2.1., and 2.2.):
- (void)scrollViewWillBeginDragging:(UIScrollView *)activeScrollView
{
barButton.enabled = NO;
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
barButton.enabled = YES;
}
And as it's explained in the previous answer, UISCrollViewDelegate needs to be set in the .h file:
#interface MyTableViewController : UITableViewController <UIScrollViewDelegate>
P.S. You can use scrollEnabled instead of userInteractionEnabled, but it all depends on what you're doing, but userInteraction is the preferred option.