UITableView Find Location of Section header? - iphone

I am trying to find out the location of a swipeGesture when swiped on a tableview.
In the swipe action, I've seen many people do this:
CGPoint location = [recognizer locationInView:tableView];
NSIndexPath* indexPath = [tableView indexPathForRowAtPoint:location];
UITableViewCell* cell = [tableView cellForRowAtIndexPath:indexPath];
But my tableView is made up of section headers that have no cells in them. Because of that, the index path method returns nil. Is there any way to find the location of the Section header? They don't have this method, but something like indexPathForSectionAtPoint: would be the kind of thing I'm looking for.
If anybody knows how to do this, it would be greatly appreciated.
Thanks,

You already have the location and the indexPath.
Now, to get the cell (and its data) you use indexPath.row, as in your example.
To get the section (with or without cell) you use indexPath.section.
If there is no row beneath the point location indexPathForRowAtPoint: will return nil, so that is expected behavior.
Section headings are not really meant to be areas of the view for interaction. Rather they are headings. The UITableView is designed to respond to interaction with its rows.
Maybe one way to fix this is to change your data structure so that the content you display in your section headings really lives in table view rows.
Another approach would be to use the UIView of the section header and have it respond to the swipe gesture. (Make sure you pass the gesture on to the table view with super.)

Ended up doing something similar, but with a tap gesture on the table section header (instead of swipe).
There are probably more elegant ways to do this, but I basically convert the gesture's view to the tableView coordinate space. Then I iterate over each section header's CGRect to see if it intersects with the gesture origin.
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let cell = tableView.dequeueReusableCellWithIdentifier("header") as! HeaderCell
let tapHeader = UITapGestureRecognizer(target: self, action: "tappedOnHeader:")
tapHeader.delegate = self
tapHeader.numberOfTapsRequired = 1
tapHeader.numberOfTouchesRequired = 1
cell.contentView.addGestureRecognizer(tapHeader)
return cell
}
func tappedOnHeader(gesture:UIGestureRecognizer){
if let cellContentView = gesture.view {
let tappedPoint = cellContentView.convertPoint(cellContentView.bounds.origin, toView: tableView)
for i in 0..<tableView.numberOfSections {
let sectionHeaderArea = tableView.rectForHeaderInSection(i)
if CGRectContainsPoint(sectionHeaderArea, tappedPoint) {
print("tapped on section header:: \(i)")
}
}
}
}
Created a git branch on another tutorial project as a working example:
https://github.com/ThornTechPublic/HorizontalScrollingCollectionView/tree/sectionHeaderTap

//Sublcass a header footer view and in .h file declare this protocol
#protocol MyProtocol <NSObject>
-(void)userTappedView:(UITableViewHeaderFooterView *)view atPoint:(CGPoint)point;
#end
//in same .h file add this
#property (weak, nonatomic) id <MyProtocol> delegate;
//In same subclass .m file do this
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesEnded:touches withEvent:event];
UITouch *touch = [[event allTouches] anyObject];
CGPoint point = [touch locationInView:self];
[self.delegate userTappedView:self atPoint:point];
}
//In controller containing tableview, conform to 'MyProtocol' like this
-(void)userTappedView:(UITableViewHeaderFooterView *)view atPoint:(CGPoint)point {
NSNumber *value = [self sectionForUserSelectionInTableView:self.tableView
atTouchLocation:point
inHeaderFooterView:view];
if (!value) {
return;
}
NSUInteger tappedSection = value.integerValue;
}
-(NSNumber *)sectionForUserSelectionInTableView:(UITableView *)tableView
atTouchLocation:(CGPoint)location
inHeaderFooterView:(UITableViewHeaderFooterView *)view {
CGPoint point = [tableView convertPoint:location
fromView:view];
for (NSInteger i = 0; i < [tableView numberOfSections]; i++) {
CGRect rect = [tableView rectForHeaderInSection:i];
if (CGRectContainsPoint(rect, point)) {
return #(i);
}
}
return nil;
}

Related

How to get UITableView from UITableViewCell?

I have a UITableViewCell which is linked to an object and I need to tell if the cell is visible. From the research I've done, this means I need to somehow access the UITableView that contains it (from there, there are several ways to check if it's visible). So I'm wondering if UITableViewCell has a pointer to the UITableView, or if there was any other way to get a pointer from the cell?
To avoid checking the iOS version, iteratively walk up the superviews from the cell's view until a UITableView is found:
Objective-C
id view = [cellInstance superview];
while (view && [view isKindOfClass:[UITableView class]] == NO) {
view = [view superview];
}
UITableView *tableView = (UITableView *)view;
Swift
var view = cellInstance.superview
while (view != nil && (view as? UITableView) == nil) {
view = view?.superview
}
if let tableView = view as? UITableView {
tableView.beginUpdates()
tableView.endUpdates()
}
In iOS7 beta 5 UITableViewWrapperView is the superview of a UITableViewCell. Also UITableView is superview of a UITableViewWrapperView.
So for iOS 7 the solution is
UITableView *tableView = (UITableView *)cell.superview.superview;
So for iOSes up to iOS 6 the solution is
UITableView *tableView = (UITableView *)cell.superview;
Swift 5 extension
Recursively
extension UIView {
func parentView<T: UIView>(of type: T.Type) -> T? {
guard let view = superview else {
return nil
}
return (view as? T) ?? view.parentView(of: T.self)
}
}
extension UITableViewCell {
var tableView: UITableView? {
return parentView(of: UITableView.self)
}
}
Using loop
extension UITableViewCell {
var tableView: UITableView? {
var view = superview
while let v = view, v.isKind(of: UITableView.self) == false {
view = v.superview
}
return view as? UITableView
}
}
Before iOS7, the cell's superview was the UITableView that contained it. As of iOS7 GM (so presumably will be in the public release as well) the cell's superview is a UITableViewWrapperView with its superview being the UITableView. There are two solutions to the problem.
Solution #1: Create a UITableViewCell category
#implementation UITableViewCell (RelatedTable)
- (UITableView *)relatedTable
{
if ([self.superview isKindOfClass:[UITableView class]])
return (UITableView *)self.superview;
else if ([self.superview.superview isKindOfClass:[UITableView class]])
return (UITableView *)self.superview.superview;
else
{
NSAssert(NO, #"UITableView shall always be found.");
return nil;
}
}
#end
This is a good drop-in replacement to using cell.superview, makes it easy to refactor your existing code -- just search and replace with [cell relatedTable], and throw in an assert to ensure that if the view hierarchy changes or reverts in the future it will show up immediately in your tests.
Solution #2: Add a Weak UITableView reference to UITableViewCell
#interface SOUITableViewCell
#property (weak, nonatomic) UITableView *tableView;
#end
This is a much better design, though it will require a bit more code refactoring to use in existing projects. In your tableView:cellForRowAtIndexPath use SOUITableViewCell as your cell class or make sure your custom cell class is subclassed from SOUITableViewCell and assign the tableView to the cell's tableView property. Inside the cell you can then refer to the containing tableview using self.tableView.
If it is visible then it has a superview. And ... surprise ... the superview is an UITableView object.
However, having a superview is no guarantee for being on screen. But UITableView provides methods to determine which cells are visible.
And no, there is no dedicated reference from a cell to a table. But when you subclass UITableViewCell you may introduce one and set it upon creation. (I did that myself a lot before I thought of the subview hierarchy.)
Update for iOS7:
Apple has changed the subview hierarchy here. As usual when working with things that are not detailled documented, there is always a risk that things change. It is far saver to "crawl up" the view hierarchy until a UITableView object is eventually found.
Swift 2.2 solution.
An extension for UIView that recursively searches for a view with a specific type.
import UIKit
extension UIView {
func lookForSuperviewOfType<T: UIView>(type: T.Type) -> T? {
guard let view = self.superview as? T else {
return self.superview?.lookForSuperviewOfType(type)
}
return view
}
}
or more compact (thanks to kabiroberai):
import UIKit
extension UIView {
func lookForSuperviewOfType<T: UIView>(type: T.Type) -> T? {
return superview as? T ?? superview?.superviewOfType(type)
}
}
In your cell you just call it:
let tableView = self.lookForSuperviewOfType(UITableView)
// Here we go
Mind that UITableViewCell is added on the UITableView only after cellForRowAtIndexPath execution.
Whatever you may end up managing to do by calling super view or via the responder chain is going to be very fragile.
The best way to do this, if the cells wants to know something, is to pass an object to the cell that responds to some method that answers the question the cell wants to ask, and have the controller implement the logic of determining what to answer (from your question I guess the cell wants to know if something is visible or not).
Create a delegate protocol in the cell, set the delegate of the cell the tableViewController and move all the ui "controlling" logic in the tableViewCotroller.
The table view cells should be dum view that will only display information.
I created a category on UITableViewCell to get its parent tableView:
#implementation UITableViewCell (ParentTableView)
- (UITableView *)parentTableView {
UITableView *tableView = nil;
UIView *view = self;
while(view != nil) {
if([view isKindOfClass:[UITableView class]]) {
tableView = (UITableView *)view;
break;
}
view = [view superview];
}
return tableView;
}
#end
Best,
Here is the Swift version based on above answers. I have generalized into ExtendedCell for later usage.
import Foundation
import UIKit
class ExtendedCell: UITableViewCell {
weak var _tableView: UITableView!
func rowIndex() -> Int {
if _tableView == nil {
_tableView = tableView()
}
return _tableView.indexPathForSelectedRow!.row
}
func tableView() -> UITableView! {
if _tableView != nil {
return _tableView
}
var view = self.superview
while view != nil && !(view?.isKindOfClass(UITableView))! {
view = view?.superview
}
self._tableView = view as! UITableView
return _tableView
}
}
Hope this help :)
I based this solution on Gabe's suggestion that UITableViewWrapperView object is the superview of UITableViewCell object in iOS7 beta5 .
Subclass UITableviewCell :
- (UITableView *)superTableView
{
return (UITableView *)[self findTableView:self];
}
- (UIView *)findTableView:(UIView *)view
{
if (view.superview && [view.superview isKindOfClass:[UITableView class]]) {
return view.superview;
}
return [self findTableView:view.superview];
}
I Borrowed and modified a little bit from the above answer and come up with the following snippet.
- (id)recursivelyFindSuperViewWithClass:(Class)clazz fromView:(id)containedView {
id containingView = [containedView superview];
while (containingView && ![containingView isKindOfClass:[clazz class]]) {
containingView = [containingView superview];
}
return containingView;
}
Passing in class offers the flexibility for traversing and getting views other than UITableView in some other occasions.
My solution to this problem is somewhat similar to other solutions, but uses an elegant for-loop and is short. It should also be future-proof:
- (UITableView *)tableView
{
UIView *view;
for (view = self.superview; ![view isKindOfClass:UITableView.class]; view = view.superview);
return (UITableView *)view;
}
UITableView *tv = (UITableView *) self.superview.superview;
BuyListController *vc = (BuyListController *) tv.dataSource;
Instead of superview, try using ["UItableViewvariable" visibleCells].
I used that in a foreach loops to loop through the cells that the app saw and it worked.
for (UITableView *v in [orderItemTableView visibleCells])//visibleCell is the fix.
{
#try{
[orderItemTableView reloadData];
if ([v isKindOfClass:[UIView class]]) {
ReviewOrderTableViewCell *cell = (ReviewOrderTableViewCell *)v;
if (([[cell deleteRecord] intValue] == 1) || ([[[cell editQuantityText] text] intValue] == 0))
//code here
}
}
}
Works like a charm.
Minimally tested but this non-generic Swift 3 example seems to work:
extension UITableViewCell {
func tableView() -> UITableView? {
var currentView: UIView = self
while let superView = currentView.superview {
if superView is UITableView {
return (superView as! UITableView)
}
currentView = superView
}
return nil
}
}
this code `UITableView *tblView=[cell superview]; will give you an instance of the UItableview which contains the tabe view cell
I suggest you traverse the view hierarchy this way to find the parent UITableView:
- (UITableView *) findParentTableView:(UITableViewCell *) cell
{
UIView *view = cell;
while ( view && ![view isKindOfClass:[UITableView class]] )
{
#ifdef DEBUG
NSLog( #"%#", [[view class ] description] );
#endif
view = [view superview];
}
return ( (UITableView *) view );
}
Otherwise your code will break when Apple changes the view hierarchy again.
Another answer that also traverses the hierarchy is recursive.
UITableViewCell Internal View Hierarchy Change in iOS 7
Using iOS 6.1 SDK
<UITableViewCell>
| <UITableViewCellContentView>
| | <UILabel>
Using iOS 7 SDK
<UITableViewCell>
| <UITableViewCellScrollView>
| | <UIButton>
| | | <UIImageView>
| | <UITableViewCellContentView>
| | | <UILabel>
The new private UITableViewCellScrollView class is a subclass of UIScrollView and is what allows this interaction:
![enter image description here][1]
[1]: http://i.stack.imgur.com/C2uJa.gif
http://www.curiousfind.com/blog/646
Thank You
You can get it with one line of code.
UITableView *tableView = (UITableView *)[[cell superview] superview];
extension UIView {
func parentTableView() -> UITableView? {
var viewOrNil: UIView? = self
while let view = viewOrNil {
if let tableView = view as? UITableView {
return tableView
}
viewOrNil = view.superview
}
return nil
}
}
from #idris answer
I wrote an expansion for UITableViewCell in Swift
extension UITableViewCell {
func relatedTableView() -> UITableView? {
var view = self.superview
while view != nil && !(view is UITableView) {
view = view?.superview
}
guard let tableView = view as? UITableView else { return nil }
return tableView
}

How to get cell indexpath in uitextfield Delegate Methods?

I have two textfields in a custom cell how to get the indexpath value of Tableview cell in textfield delegate methods I want to get the input value from user and save it to the relavent object. the user can add more cells by clicking button(Add More) in cell..
Thanks in Advance...
Update to iOS7!
With new features in iOS7 now code should be :
UITableViewCell *textFieldRowCell;
if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_6_1) {
// Load resources for iOS 6.1 or earlier
textFieldRowCell = (UITableViewCell *) textField.superview.superview;
} else {
// Load resources for iOS 7 or later
textFieldRowCell = (UITableViewCell *) textField.superview.superview.superview;
// TextField -> UITableVieCellContentView -> (in iOS 7!)ScrollView -> Whoola!
}
NSIndexPath *indexPath = [self.tableView indexPathForCell:textFieldRowCell];
A more dynamic solution (no hardcoded superview levels and same code for different iOS versions).
Further, indexPathForCell: will not work if the cell is not visible, therefore I use indexPathForRowAtPoint: as workaround.
//find the UITableViewcell superview
UIView *cell = textField;
while (cell && ![cell isKindOfClass:[UITableViewCell class]])
cell = cell.superview;
//use the UITableViewcell superview to get the NSIndexPath
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:cell.center];
This is how I have been doing it and have been having better luck. I grab the origin of the textField frame. Convert that to a point. Then convert the point to an index path.
-(void)textFieldDidBeginEditing:(UITextField *)textField
{
CGPoint origin = textField.frame.origin;
CGPoint point = [textField.superview convertPoint:origin toView:self.tableView];
NSIndexPath * indexPath = [self.tableView indexPathForRowAtPoint:point];
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionMiddle animated:YES];
}
Try this method to get textfield dynamically anywhere from your tableview controller
#pragma mark - Get textfield indexpath
- (NSIndexPath *)TextFieldIndexpath:(UITextField *)textField
{
CGPoint point = [textField.superview convertPoint:textField.frame.origin toView:self.TblView];
NSIndexPath * indexPath = [self.TblView indexPathForRowAtPoint:point];
NSLog(#"Indexpath = %#", indexPath);
return indexPath;
}
To get The indexPath try the following code.
UIView *contentView = (UIVIew *)[textfield superview];
UITableViewCell *cell = (UITableViewCell *)[contentView superview];
if(IS_IOS7_OR_GREATER) {
cell = (UITableViewCell *)[[contentView superview] superview];
}
NSIndexPath *indexPath = [self.tableview indexPathForCell:cell];
Tats it you are done.
To be simple,
NSIndexPath *indexPath = [self.tableview indexPathForCell:(UITableViewCell *)[(UIVIew *)[textfield superview] superview]];
if(IS_IOS7_OR_GREATER) {
cell = (UITableViewCell *)[[[textfield superview] superview] superview];
}
Check the updated answer.
set cell indexpath value to UITextField tag property and you can access the indexpath in delegate methods like textfield.tag
You can set the tags of textfields in cellForRowAtIndexPath: such that it stores info of both cell and text field
For example : If it is cell in 4th row, tag of 1st and 2nd textfields can be 41 and 42 respectively. Similarly, tags of textfields should be 51 and 52 for 5th row and so on...
Then in textfield delegate method, you can get textfield.tag to identify active textfield.
This can be done in the Objective-C runtime for any instance (doesn't have to be UITextField), with any associated object (doesn't have to be NSIndexPath).
For this question, we could create a category UIView+RepresentingIndexPath.
Our interface allows us to set and retrieve an NSIndexPath:
#interface UIView (RepresentingIndexPath)
- (void)representIndexPath:(NSIndexPath *)indexPath;
- (NSIndexPath *)representedIndexPath;
#end
Our implementation uses Objective-C associated objects to set and retrieve an index path on a view:
#import "UIView+RepresentingIndexPath.h"
#import <objc/runtime.h>
static char IndexPathKey;
#implementation UIView (RepresentingIndexPath)
- (void)representIndexPath:(NSIndexPath *)indexPath
{
objc_setAssociatedObject(self, &IndexPathKey, indexPath, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSIndexPath *)representedIndexPath
{
return objc_getAssociatedObject(self, &IndexPathKey);
}
#end
In action:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
TextFieldTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"TextFieldCell" forIndexPath:indexPath];
[cell.textField addTarget:self action:#selector(textFieldTextChanged:) forControlEvents:UIControlEventEditingChanged];
[cell.textField representIndexPath:indexPath];
return cell;
}
- (void)textFieldTextChanged:(UITextField *)sender
{
NSIndexPath *indexPath = [sender representedIndexPath];
NSLog(#"%#", indexPath);
}
💣
One final note! Messing around in the runtime should really be avoided if you can achieve what you're trying to do without doing so. Just thought I'd add another solution!
I find this answer searching how can I find the index path of a cell with inside a UITextField.
So, thanks to the answer above, I put my code here, hoping might be usefull.
- (void)searchSelectedIndexPath:(UIView*)view {
// This allow to find selected index path for a table view cell with a text field inside.
for (UIView* subview in view.subviews) {
if ([view isKindOfClass:[UITextField class]]) {
if ([view isFirstResponder]) {
UIView *cell = view;
while (cell && ![cell isKindOfClass:[UITableViewCell class]]) {
cell = cell.superview;
}
self.selectedIndexPath = [self.tableView indexPathForRowAtPoint:cell.center];
return;
}
}
[self searchSelectedIndexPath:subview];
}
}
In this way, when keyboard notification will be raise:
- (void)keyboardDidShow:(NSNotification*)notification {
[self searchSelectedIndexPath:self.tableView];
}
In case somebody like me needs #Kerkness' answer (this one really worked for me) in swift:
func textFieldDidBeginEditing(_ textField: UITextField) {
let origin: CGPoint = textField.frame.origin
let point: CGPoint? = textField.superview?.convert(origin, to: tableView)
let indexPath: IndexPath? = tableView.indexPathForRow(at: point ?? CGPoint.zero)
tableView.scrollToRow(at: indexPath!, at: .middle, animated: true)
}
It should be straight forward enough: you get the point then you get the indexPath and do whatever you need with it!
Thanks, #Luka, it works in a great way.
Here is the swift 4 solution,
var selectedIndexPath: IndexPath?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShowHide(_ :)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShowHide(_ :)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
func searchSelectedIndexPath(view: UIView) {
view.subviews.forEach { (subview) in
if view is UITextView, view.isFirstResponder == true {
var cell:UIView? = view;
while cell != nil && !(cell is UITableViewCell) {
cell = cell?.superview;
}
if cell != nil {
self.selectedIndexPath = self.dashBoardTableView.indexPathForRow(at: (cell?.center)!)
return
}
}
self.searchSelectedIndexPath(view: subview)
}
}
// Keyboard notification observer menthod
#objc fileprivate func keyboardWillShowHide(_ notification: NSNotification){
if notification.name == NSNotification.Name.UIKeyboardWillShow {
UIView.animate(withDuration: duration, animations: { () -> Void in
self.selectedIndexPath = nil;
self.searchSelectedIndexPath(view: self.tableView)
if let indexpath = self.selectedIndexPath {
self.tableView.scrollToRow(at: indexpath, at: .top, animated: false)
} else{
self.bottomContriant.constant = keyboardHeight
self.view.layoutSubviews()
}
})
} else {
self.bottomContriant.constant = 15
UIView.animate(withDuration: duration, animations: { () -> Void in
self.view.layoutIfNeeded()
})
}
}

UIScrollView inside UITableViewCell touch detect

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
*/

Custom accessory view button - wrong row selected

I have implemented the accessory view of a UITableViewCell as a button, in the
button's selector method i have the following code
- (UIButton *) makeDetailDisclosureButton
{
UIButton * button = [UIButton buttonWithType:UIButtonTypeCustom];
UIImage* image = [UIImage imageNamed:#"custom.png"];
[button setImage:image forState:UIControlStateNormal];
button.frame = CGRectMake(0.0, 0.0, image.size.width, image.size.height);
[button addTarget: self
action: #selector(accessoryButtonTapped:withEvent:)
forControlEvents: UIControlEventTouchUpInside];
return ( button );
}
- (void) accessoryButtonTapped: (UIControl *)button withEvent:(UIEvent *) event
{
NSSet *touches = [event allTouches];
UITouch *touch = [touches anyObject];
CGPoint currentTouchPosition = [touch locationInView:self.tableView];
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:currentTouchPosition];
if (indexPath != nil)
{
[self tableView: self.tableView accessoryButtonTappedForRowWithIndexPath: indexPath];
}
}
However sometimes the wrong row is being selected, ie if I tap on row 0 in the table i get
row = 1 actually being selected. The code above is quite well known as a solution for
a custom accessory view but it is proving unreliable. Is there something else i need to do here?
Try this,
- (void) accessoryButtonTapped: (UIControl *)button withEvent:(UIEvent *) event
{
UITableViewCell *cell = (UITableViewCell*)button.superview.superview;
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
[self tableView:self.tableView accessoryButtonTappedForRowWithIndexPath:indexPath];
}
The idea here is pretty basic. Button's superview should give me cell.contentView and it's superview is the cell. There is lovely helper method indexPathForCell: which will give us the index path which we can use to pass to the delegate method.
Nothing wrong seen in your code but you can refer this tutorial for reference.
Difference I see in your code and in tutorial is
- (void) accessoryButtonTapped: (UIControl *)button withEvent:(UIEvent *) event //Your event
- (void)checkButtonTapped:(id)sender event:(id)event //In tutorial.
This does not make difference but still just a thought.
Hope it helps.
What do you have at tableView:accessoryButtonTappedForRowWithIndexPath: method?
Anyhow, I wouldn't mess with the touches to get the index path.
Instead I'd tag the button with the [indexPath row] and then pass the tag to the other method.
I had a similar problem: The index path row in tableView:accessoryButtonTappedForRowWithIndexPath: was consistently wrong for -- in my case -- every 5th row of the table view.
e.g., tapping the Detail Disclosure button for row 4 correctly gave me indexPath.row = 3; but tapping row 5 also gave me indexPath.row = 3.
My mistake related to having set incompatible row heights for my custom UITableViewCell.
i.e., I set the custom row height to 56 in the UITableViewCell's Size Inspector, but I forgot that I earlier had set the row height programmatically in tableView:heightForRowAtIndexPath: as a multiple of tableView.rowHeight. The default tableView.rowHeight (editable in the UITableView's Size Inspector) is 44, and that 'clash' apparently caused the problem.
I resolved my problem by setting the row height fields to 56 in the Size Inspectors of both the UITableView & the UITableViewCell and commenting out the tableView:heightForRowAtIndexPath: method.

get section number and row number on custom cells button click?

i have tableview with custom cell.the table is divided in many section and rows.i have a custom button on cell. now i want to get section number and row number when i click on that button.?
any idea regarding this
You'll need to implement a UIControl event-handling method on your view controller, and set that as the handler for all your buttons. i.e. inside your -tableView:cellForRowAtIndexPath: function you would do something like:
[theCell.button addTarget: self
action: #selector(buttonPressed:withEvent:)
forControlEvents: UIControlEventTouchUpInside];
Then your event handler would look like this:
- (void) buttonPressed: (id) sender withEvent: (UIEvent *) event
{
UITouch * touch = [[event touches] anyObject];
CGPoint location = [touch locationInView: self.tableView];
NSIndexPath * indexPath = [self.tableView indexPathForRowAtPoint: location];
/* indexPath contains the index of the row containing the button */
/* do whatever it is you need to do with the row data now */
}
A few thoughts:
You can iterate through the button's superview hierarchy until you find a UITableViewCell, then call - (NSIndexPath *)indexPathForCell:(UITableViewCell *)cell on the UITableView.
- (void)buttonClicked:(id)sender {
UIView *button = sender;
for (UIView *parent = [button superview]; parent != nil; parent = [parent superview]) {
if ([parent isKindOfClass: [UITableViewCell class]]) {
UITableViewCell *cell = (UITableViewCell *) parent;
NSIndexPath *path = [self.tableView indexPathForCell: cell];
// now use the index path
break; // for
}
}
}
You can alternately use the tag of the button to store an index referencing the row. This only holds a single integer, so it makes the most sense when you have a single section, or when you are managing your rows as a flat list.
You can alternately subclass UITableViewCell to encapsulate the button. Your UITableViewCell could respond to the button events and rebroadcast an event to its own delegate, passing self. The event delegate can then call - (NSIndexPath *)indexPathForCell:(UITableViewCell *)cell on the UITableView to get the index path.
The following method is called when you select a cell in your tableView:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
to access the section number: int section = indexPath.section;
to access the row number (within the correct section): int row = indexPath.row;
The UITableView can convert a CGPoint coordinate into an indexPath:
-(NSIndexPath *)indexPathForRowAtPoint:(CGPoint)point
Add can instance variable of your UITableViewCell subclassTo store the index path of the cell:
NSIndexPath *myIndexPath;
When you create the cell in:
cellForIndexPath:
pass in the indexpath to the newly created/recycled cell.
Now when you press the button, just read the indexpath from the ivar of your cell.