Get current pinned section of UITableView - ios5

What is the most accurate way to get the title or index of the top most section in the UITableView? I don't mean the section at index 0, but the section header that is currently pinned at the top while scrolling.

You can get the section at the top of the screen like so:
NSUInteger sectionNumber = [[tableView indexPathForCell:[[tableView visibleCells] objectAtIndex:0]] section];
But, this method might not be optimal for you because it will get the number will weird when it is in the transition period between two headers. As long as you don't need to know the section during the transition you should be fine.
I hope this is what your looking for,

i believe that instead of querying for the visibleCells, it's better to ask for the index paths the following way:
NSInteger section = [[[self.tableView indexPathsForVisibleRows] firstObject] section];
You can use this code in one of the UIScrollViewDelegate methods according to your needs.

Swift version
Use first (current pinned) or last (last displayed)
if let section:Int = tableView.indexPathsForVisibleRows?.last?.section {
tableView.scrollToRowAtIndexPath(NSIndexPath(forRow: 0, inSection: section), atScrollPosition: UITableViewScrollPosition.None, animated: true)
}

Have your class implement the UIScrollViewDelegate, and implement the following functions.
It will ensure you retrieve section only when tableview stops scrolling.
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
if (!decelerate)
{
// retrieve section here
section = [[[self.tableview visibleCells] objectAtIndex:0] section];
}
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
// retrieve section here
section = [[[self.tableview visibleCells] objectAtIndex:0] section];
}

Related

Create infinite scroll effect by inserting new rows [duplicate]

How do I do an infinite scrolling in a UITableView? I know how to do it using a UIScrollView, in which apple has demonstrated in one of the WWDC's video. I tried doing the following in tableView:cellForRowAtIndexPath::
if (indexPath.row == [self.newsFeedData_ count] - 1)
{
[self.newsFeedData_ addObjectsFromArray:self.newsFeedData_];
[self.tableView reloadData];
}
but this fails. Any other idea?
If you need to know when you hit the bottom of the UITableView, become it's delegate (because it is a subclass of UIScrollView), and use the -scrollViewDidScroll: delegate method to compare the table's content height and it's actual scroll position.
EDIT (something like this):
- (void)scrollViewDidScroll:(UIScrollView *)scrollView_
{
CGFloat actualPosition = scrollView_.contentOffset.y;
CGFloat contentHeight = scrollView_.contentSize.height - (someArbitraryNumber);
if (actualPosition >= contentHeight) {
[self.newsFeedData_ addObjectsFromArray:self.newsFeedData_];
[self.tableView reloadData];
}
}
You can support infinite scroll with pull to refresh at the top and/or scroll continuously at the bottom with a spinner wheel using:
https://github.com/samvermette/SVPullToRefresh
SVPullToRefresh handles the logic when UITableView reaches the bottom. A spinner is shown automatically and a callback block is fired. You add in your business logic to the callback block.
Here's an example:
#import "UIScrollView+SVInfiniteScrolling.h"
// ...
[tableView addInfiniteScrollingWithActionHandler:^{
// append data to data source, insert new cells at the end of table view
// call [tableView.infiniteScrollingView stopAnimating] when done
}];
This project can be added to your project using CocoaPods or directly compiled into your project.
Here's a very quick and complete demo of an infinite scrolling UITableView I put together...
#interface InfiniteScrollViewController ()
#property (nonatomic) NSMutableArray *tableViewData;
#property (nonatomic) BOOL loadingMoreTableViewData;
#end
#implementation InfiniteScrollViewController
- (void)viewDidLoad {
self.tableViewData = [[NSMutableArray alloc] init];
[self addSomeMoreEntriesToTableView];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.tableViewData.count + 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
if (indexPath.row < self.tableViewData.count) {
cell.textLabel.text = [self.tableViewData objectAtIndex:indexPath.row];
} else {
cell.textLabel.text = #"Loading more data...";
// User has scrolled to the bottom of the list of available data so simulate loading some more if we aren't already
if (!self.loadingMoreTableViewData) {
self.loadingMoreTableViewData = YES;
[self performSelector:#selector(addSomeMoreEntriesToTableView) withObject:nil afterDelay:5.0f];
}
}
return cell;
}
- (void)addSomeMoreEntriesToTableView {
int loopTill = self.tableViewData.count + 20;
while (self.tableViewData.count < loopTill) {
[self.tableViewData addObject:[NSString stringWithFormat:#"%i", self.tableViewData.count]];
};
self.loadingMoreTableViewData = NO;
[self.tableView reloadData];
}
#end
'UITableView' is same as 'UIScrollView' in 'scrollViewDidScroll' method.
So, its easy to emulate infinite scrolling.
double the array so that head and tail are joined together to emulate circular table
use my following code to make user switch between 1st part of doubled table and 2nd part of doubled table when they tend to reach the start or the end of the table.
:
/* To emulate infinite scrolling...
The table data was doubled to join the head and tail: (suppose table had 1,2,3,4)
1 2 3 4|1 2 3 4 (actual data doubled)
---------------
1 2 3 4 5 6 7 8 (visualising joined table in eight parts)
When the user scrolls backwards to 1/8th of the joined table, user is actually at the 1/4th of actual data, so we scroll instantly (we take user) to the 5/8th of the joined table where the cells are exactly the same.
Similarly, when user scrolls to 6/8th of the table, we will scroll back to 2/8th where the cells are same. (I'm using 6/8th when 7/8th sound more logical because 6/8th is good for small tables.)
In simple words, when user reaches 1/4th of the first half of table, we scroll to 1/4th of the second half, when he reaches 2/4th of the second half of table, we scroll to the 2/4 of first half. This is done simply by subtracting OR adding half the length of the new/joined table.
*/
-(void)scrollViewDidScroll:(UIScrollView *)scrollView_
{
CGFloat currentOffsetX = scrollView_.contentOffset.x;
CGFloat currentOffSetY = scrollView_.contentOffset.y;
CGFloat contentHeight = scrollView_.contentSize.height;
if (currentOffSetY < (contentHeight / 8.0)) {
scrollView_.contentOffset = CGPointMake(currentOffsetX,(currentOffSetY + (contentHeight/2)));
}
if (currentOffSetY > ((contentHeight * 6)/ 8.0)) {
scrollView_.contentOffset = CGPointMake(currentOffsetX,(currentOffSetY - (contentHeight/2)));
}
}
P.S. - I've used this code on one of my apps called NT Time Table (Lite). If you want the preview, you can check out the app: https://itunes.apple.com/au/app/nt-time-table-lite/id528213278?mt=8
If your table can sometimes be too short, at the beginning of the above method you can add a if logic to exit the method when data count is say for example less than 9.
For me worked better scrollViewDidEndDragging: than scrollViewDidScroll:.
The second approach will send you each position during scroll and cause, if you are fetching remote resources you will hit your endpoint several times, which is not good.
Complete example based on #codafi solution with comments from #danielgomezrico about how to calculate contentHeight:
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView
willDecelerate:(BOOL)decelerate {
CGFloat actualPosition = scrollView.contentOffset.y;
CGFloat contentHeight = scrollView.contentSize.height - (self.tableView.frame.size.height);
if (actualPosition >= contentHeight) {
// fetch resources
[self.tableView reloadData];
}
}
Generally I override scrollViewDidEndDecelerating and inside it I put my code to request more data.
Example:
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
float endScrolling = scrollView.contentOffset.y + scrollView.frame.size.height;
if (endScrolling >= scrollView.contentSize.height){
//put here your code
}
}
Recently I uploaded on GitHub a subclass of UITableView, that implements the infinite scroll.
You can download it here:
https://github.com/alchimya/iOS-LazyTableView
rather than overriding we can do this optimally in layoutSubviews.
Here's how I got it implemented. You can get to know more about the implementation here
- (void)layoutSubviews{
[super layoutSubviews];
if(self.delegateForViews){
CGPoint contentOffset = self.contentOffset;
if([self.delegateForViews noOfViews]>numOfReusableViews){
NSUInteger centerIndex=visibleViews.count/2;
NSUInteger noOfViews=[self.delegateForViews noOfViews];
UIView *centerView=[visibleViews objectAtIndex:centerIndex];
CGPoint centerViewOrigin=centerView.frame.origin;
CGSize centerViewSize=centerView.frame.size;
CGFloat offsetDifference=contentOffset.x-centerViewOrigin.x;
CGFloat offsetDifferenceAbs=fabs(contentOffset.x-centerViewOrigin.x);
if(offsetDifferenceAbs>=centerViewSize.width){
if(offsetDifference<0){
currentPosition--;
}else{
currentPosition++;
}
self.contentOffset=centerViewOrigin;
currentPosition=[self getPosition:currentPosition noOfViews:noOfViews];
[self.delegateForViews clearView:centerView];
[self.delegateForViews setupView:centerView forPosition:currentPosition];
for (int i=centerIndex-1; i>=0; i--) {
UIView* prevView=[visibleViews objectAtIndex:i];
[self.delegateForViews clearView:prevView];
[self.delegateForViews setupView:prevView forPosition:
[self getPosition:currentPosition-1 noOfViews:noOfViews]];
}
for (int i=centerIndex+1; i<visibleViews.count; i++) {
UIView* nextView=[visibleViews objectAtIndex:i];
[self.delegateForViews clearView:nextView];
[self.delegateForViews setupView:nextView forPosition:
[self getPosition:currentPosition+1 noOfViews:noOfViews]];
}
}
}
}
}
One of the simple and that offered me everything i need is this class:
https://github.com/jakemarsh/JMStatefulTableViewController
You just need to subclass JMStatefulTableViewController and the it has 3 methods that you need to overwrite:
one that is called on init, to get the initial data
statefulTableViewControllerWillBeginInitialLoading
one when the user pull to refresh
statefulTableViewControllerWillBeginLoadingFromPullToRefresh
one when is called for the infinite scroll (next page)
statefulTableViewControllerWillBeginLoadingNextPage
This can be used from Cocoapods too.
scrollviewDidScroll will call when you move through the rows in tableview
func scrollViewDidScroll(_ scrollView: UIScrollView) {
//check for the visible rows
let indexpath = self.tableView.indexPathsForVisibleRows?.last
//check if the visible row last is equal to the total number of counts
if(indexpath?.last == self.listCount){
//code for adding data to the tableview and reload the table view.
}
}
look in the link for more details about indexPathForVisibleRows
https://developer.apple.com/documentation/uikit/uitableview/1614885-indexpathsforvisiblerows

Detect Distance between UITableView Section Headers

In a plain UITableView with custom UIView's as section headers, is there a way to calculate:
When one of the Section is on the top, the distance between that section and the next one that would come?
I am expecting to calculate this here:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
You can find the number of rows in that section by calling the tableView:numberOfRowsInSection: method from the UITableViewDataSourceDelegate protocol. You can get the height for each row in the section with the tableView:heightForRowAtIndexPath: method from the UITableViewDelegate protocol. Add up the height for all the rows and you have the distance you want.
Your code would look something like this, assuming you have a reference to the tableview and the section.
float totalHeight = 0;
for (int i = 0; i < [tableViewDataSourceDelegate
tableView:tableView
numberOfRowsInSection:section]; i ++) {
totalHeight += [tableViewDelegate
tableView:tableView
heightForRowAtIndexPath:[NSIndexPath
indexPathForRow:i
inSection:section]];
}
Haven't had a chance to test this code, but it Should Work[tm].
Edit
This will only work if the header is at the top.
(assuming all rows are same height)
NSIndexPath *topCellIndexPath = [tableView indexPathsForVisibleRows][0];
UITableViewCell *topCell = [tableView cellForRowAtIndexPath: topCellIndexPath];
CGFloat distanceToNextSection = [tableView convertRect: [topCell frame] fromView: topCell.superview].origin.y - tableView.contentOffset.y + ([self tableView: tableView numberOfRowsInSection: topCellIndexPath.section] - topCellIndexPath.row)*tableView.rowHeight
I was able to solve this by doing the following:
Before creating an section header, check if you have the section header for a given section. If you do return it from the NSMutableArray. If not keep going.
When you create the section header, keep a reference to it in a NSMutableArray.
When you scroll in:
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
Do the following:
// Get the toppest section
NSUInteger sectionNumber = [[self.tableView indexPathForCell:[[self.tableView visibleCells] objectAtIndex: 0]] section];
// Get a reference to it
SFBasicSectionHeader *topHeader = [arrayOfWeakHeaders objectAtIndex:sectionNumber];
SFBasicSectionHeader *bellowHeader;
// Check if it's Ok to get the bellow header
if (sectionNumber+1<[arrayOfWeakHeaders count] && [arrayOfWeakHeaders objectAtIndex:sectionNumber +1])
{
bellowHeader = [arrayOfWeakHeaders objectAtIndex:sectionNumber+1];
}
The difference between both will be:
CGFloat differenceBetweenTopAndBellowSection = bellowHeader.frame.origin.y - topHeader.frame.size.height - self.tableView.contentOffset.y;
Done.

UITableView infinite scrolling

How do I do an infinite scrolling in a UITableView? I know how to do it using a UIScrollView, in which apple has demonstrated in one of the WWDC's video. I tried doing the following in tableView:cellForRowAtIndexPath::
if (indexPath.row == [self.newsFeedData_ count] - 1)
{
[self.newsFeedData_ addObjectsFromArray:self.newsFeedData_];
[self.tableView reloadData];
}
but this fails. Any other idea?
If you need to know when you hit the bottom of the UITableView, become it's delegate (because it is a subclass of UIScrollView), and use the -scrollViewDidScroll: delegate method to compare the table's content height and it's actual scroll position.
EDIT (something like this):
- (void)scrollViewDidScroll:(UIScrollView *)scrollView_
{
CGFloat actualPosition = scrollView_.contentOffset.y;
CGFloat contentHeight = scrollView_.contentSize.height - (someArbitraryNumber);
if (actualPosition >= contentHeight) {
[self.newsFeedData_ addObjectsFromArray:self.newsFeedData_];
[self.tableView reloadData];
}
}
You can support infinite scroll with pull to refresh at the top and/or scroll continuously at the bottom with a spinner wheel using:
https://github.com/samvermette/SVPullToRefresh
SVPullToRefresh handles the logic when UITableView reaches the bottom. A spinner is shown automatically and a callback block is fired. You add in your business logic to the callback block.
Here's an example:
#import "UIScrollView+SVInfiniteScrolling.h"
// ...
[tableView addInfiniteScrollingWithActionHandler:^{
// append data to data source, insert new cells at the end of table view
// call [tableView.infiniteScrollingView stopAnimating] when done
}];
This project can be added to your project using CocoaPods or directly compiled into your project.
Here's a very quick and complete demo of an infinite scrolling UITableView I put together...
#interface InfiniteScrollViewController ()
#property (nonatomic) NSMutableArray *tableViewData;
#property (nonatomic) BOOL loadingMoreTableViewData;
#end
#implementation InfiniteScrollViewController
- (void)viewDidLoad {
self.tableViewData = [[NSMutableArray alloc] init];
[self addSomeMoreEntriesToTableView];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.tableViewData.count + 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
if (indexPath.row < self.tableViewData.count) {
cell.textLabel.text = [self.tableViewData objectAtIndex:indexPath.row];
} else {
cell.textLabel.text = #"Loading more data...";
// User has scrolled to the bottom of the list of available data so simulate loading some more if we aren't already
if (!self.loadingMoreTableViewData) {
self.loadingMoreTableViewData = YES;
[self performSelector:#selector(addSomeMoreEntriesToTableView) withObject:nil afterDelay:5.0f];
}
}
return cell;
}
- (void)addSomeMoreEntriesToTableView {
int loopTill = self.tableViewData.count + 20;
while (self.tableViewData.count < loopTill) {
[self.tableViewData addObject:[NSString stringWithFormat:#"%i", self.tableViewData.count]];
};
self.loadingMoreTableViewData = NO;
[self.tableView reloadData];
}
#end
'UITableView' is same as 'UIScrollView' in 'scrollViewDidScroll' method.
So, its easy to emulate infinite scrolling.
double the array so that head and tail are joined together to emulate circular table
use my following code to make user switch between 1st part of doubled table and 2nd part of doubled table when they tend to reach the start or the end of the table.
:
/* To emulate infinite scrolling...
The table data was doubled to join the head and tail: (suppose table had 1,2,3,4)
1 2 3 4|1 2 3 4 (actual data doubled)
---------------
1 2 3 4 5 6 7 8 (visualising joined table in eight parts)
When the user scrolls backwards to 1/8th of the joined table, user is actually at the 1/4th of actual data, so we scroll instantly (we take user) to the 5/8th of the joined table where the cells are exactly the same.
Similarly, when user scrolls to 6/8th of the table, we will scroll back to 2/8th where the cells are same. (I'm using 6/8th when 7/8th sound more logical because 6/8th is good for small tables.)
In simple words, when user reaches 1/4th of the first half of table, we scroll to 1/4th of the second half, when he reaches 2/4th of the second half of table, we scroll to the 2/4 of first half. This is done simply by subtracting OR adding half the length of the new/joined table.
*/
-(void)scrollViewDidScroll:(UIScrollView *)scrollView_
{
CGFloat currentOffsetX = scrollView_.contentOffset.x;
CGFloat currentOffSetY = scrollView_.contentOffset.y;
CGFloat contentHeight = scrollView_.contentSize.height;
if (currentOffSetY < (contentHeight / 8.0)) {
scrollView_.contentOffset = CGPointMake(currentOffsetX,(currentOffSetY + (contentHeight/2)));
}
if (currentOffSetY > ((contentHeight * 6)/ 8.0)) {
scrollView_.contentOffset = CGPointMake(currentOffsetX,(currentOffSetY - (contentHeight/2)));
}
}
P.S. - I've used this code on one of my apps called NT Time Table (Lite). If you want the preview, you can check out the app: https://itunes.apple.com/au/app/nt-time-table-lite/id528213278?mt=8
If your table can sometimes be too short, at the beginning of the above method you can add a if logic to exit the method when data count is say for example less than 9.
For me worked better scrollViewDidEndDragging: than scrollViewDidScroll:.
The second approach will send you each position during scroll and cause, if you are fetching remote resources you will hit your endpoint several times, which is not good.
Complete example based on #codafi solution with comments from #danielgomezrico about how to calculate contentHeight:
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView
willDecelerate:(BOOL)decelerate {
CGFloat actualPosition = scrollView.contentOffset.y;
CGFloat contentHeight = scrollView.contentSize.height - (self.tableView.frame.size.height);
if (actualPosition >= contentHeight) {
// fetch resources
[self.tableView reloadData];
}
}
Generally I override scrollViewDidEndDecelerating and inside it I put my code to request more data.
Example:
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
float endScrolling = scrollView.contentOffset.y + scrollView.frame.size.height;
if (endScrolling >= scrollView.contentSize.height){
//put here your code
}
}
Recently I uploaded on GitHub a subclass of UITableView, that implements the infinite scroll.
You can download it here:
https://github.com/alchimya/iOS-LazyTableView
rather than overriding we can do this optimally in layoutSubviews.
Here's how I got it implemented. You can get to know more about the implementation here
- (void)layoutSubviews{
[super layoutSubviews];
if(self.delegateForViews){
CGPoint contentOffset = self.contentOffset;
if([self.delegateForViews noOfViews]>numOfReusableViews){
NSUInteger centerIndex=visibleViews.count/2;
NSUInteger noOfViews=[self.delegateForViews noOfViews];
UIView *centerView=[visibleViews objectAtIndex:centerIndex];
CGPoint centerViewOrigin=centerView.frame.origin;
CGSize centerViewSize=centerView.frame.size;
CGFloat offsetDifference=contentOffset.x-centerViewOrigin.x;
CGFloat offsetDifferenceAbs=fabs(contentOffset.x-centerViewOrigin.x);
if(offsetDifferenceAbs>=centerViewSize.width){
if(offsetDifference<0){
currentPosition--;
}else{
currentPosition++;
}
self.contentOffset=centerViewOrigin;
currentPosition=[self getPosition:currentPosition noOfViews:noOfViews];
[self.delegateForViews clearView:centerView];
[self.delegateForViews setupView:centerView forPosition:currentPosition];
for (int i=centerIndex-1; i>=0; i--) {
UIView* prevView=[visibleViews objectAtIndex:i];
[self.delegateForViews clearView:prevView];
[self.delegateForViews setupView:prevView forPosition:
[self getPosition:currentPosition-1 noOfViews:noOfViews]];
}
for (int i=centerIndex+1; i<visibleViews.count; i++) {
UIView* nextView=[visibleViews objectAtIndex:i];
[self.delegateForViews clearView:nextView];
[self.delegateForViews setupView:nextView forPosition:
[self getPosition:currentPosition+1 noOfViews:noOfViews]];
}
}
}
}
}
One of the simple and that offered me everything i need is this class:
https://github.com/jakemarsh/JMStatefulTableViewController
You just need to subclass JMStatefulTableViewController and the it has 3 methods that you need to overwrite:
one that is called on init, to get the initial data
statefulTableViewControllerWillBeginInitialLoading
one when the user pull to refresh
statefulTableViewControllerWillBeginLoadingFromPullToRefresh
one when is called for the infinite scroll (next page)
statefulTableViewControllerWillBeginLoadingNextPage
This can be used from Cocoapods too.
scrollviewDidScroll will call when you move through the rows in tableview
func scrollViewDidScroll(_ scrollView: UIScrollView) {
//check for the visible rows
let indexpath = self.tableView.indexPathsForVisibleRows?.last
//check if the visible row last is equal to the total number of counts
if(indexpath?.last == self.listCount){
//code for adding data to the tableview and reload the table view.
}
}
look in the link for more details about indexPathForVisibleRows
https://developer.apple.com/documentation/uikit/uitableview/1614885-indexpathsforvisiblerows

How to know the UITableview row number

I have a UITableViewCell with UISwitch as accessoryview of each cell. When I change the value of the switch in a cell, how can I know in which row the switch is? I need the row number in the switch value changed event.
Tags, subclasses, or view hierarchy navigation are too much work!. Do this in your action method:
CGPoint hitPoint = [sender convertPoint:CGPointZero toView:self.tableView];
NSIndexPath *hitIndex = [self.tableView indexPathForRowAtPoint:hitPoint];
Works with any type of view, multi section tables, whatever you can throw at it - as long as the origin of your sender is within the cell's frame (thanks rob!), which will usually be the case.
And here it is in a UITableView Swift extension:
extension UITableView {
func indexPath(for view: UIView) -> IndexPath? {
let location = view.convert(CGPoint.zero, to: self)
return self.indexPathForRow(at: location)
}
}
If you set the tag property to the row number (as suggested by other answers), you have to update it every time in tableView:cellForRowAtIndexPath: (because a cell can be reused for different rows).
Instead, when you need the row number, you can walk up the superview chain from the UISwitch (or any other view) to the UITableViewCell, and then to the UITableView, and ask the table view for the index path of the cell:
static NSIndexPath *indexPathForView(UIView *view) {
while (view && ![view isKindOfClass:[UITableViewCell class]])
view = view.superview;
if (!view)
return nil;
UITableViewCell *cell = (UITableViewCell *)view;
while (view && ![view isKindOfClass:[UITableView class]])
view = view.superview;
if (!view)
return nil;
UITableView *tableView = (UITableView *)view;
return [tableView indexPathForCell:cell];
}
This doesn't require anything in tableView:cellForRowAtIndexPath:.
in cellForRowAtIndexPath:, set the tag property of your control to indexPath.row
Accepted solution is a clever hack.
However why do we need to use hitpoint if we can utilize already available tag property on UIView? You would say that tag can store only either row or section since its a single Int.
Well... Don't forget your roots guys (CS101).
A single Int can store two twice-smaller size integers.
And here is an extension for this:
extension Int {
public init(indexPath: IndexPath) {
var marshalledInt: UInt32 = 0xffffffff
let rowPiece = UInt16(indexPath.row)
let sectionPiece = UInt16(indexPath.section)
marshalledInt = marshalledInt & (UInt32(rowPiece) << 16)
marshalledInt = marshalledInt + UInt32(sectionPiece)
self.init(bitPattern: UInt(marshalledInt))
}
var indexPathRepresentation: IndexPath {
let section = self & 0x0000ffff
let pattern: UInt32 = 0xffff0000
let row = (UInt32(self) & pattern) >> 16
return IndexPath(row: Int(row), section: Int(section))
}
}
In your tableView(_:, cellForRowAt:) you can then:
cell.yourSwitch.tag = Int(indexPath: indexPath)
And then in the action handler you would can:
func didToogle(sender: UISwitch){
print(sender.tag.indexPathRepresentation)
}
However please note it's limitation: row and section need to be not larger then 65535. (UInt16.max)
I doubt your tableView's indexes will go that high but in case they do, challenge yourself and implement more efficient packing scheme. Say if we have a section very small, we don't need all 16 bits to represent a section. We can have our int layout like:
{section area length}{all remaining}[4 BITS: section area length - 1]
that is our 4 LSBs indicate the length of section area - 1, given that we allocate at least 1 bit for a section. Thus in case of our section is 0, the row can occupy up to 27 bits ([1][27][4]), which definitely should be enough.
I prefer using subviews, if you know your layout it's generally super simple and 1 line short...
NSIndexPath *indexPath = [tableView indexPathForCell:(UITableViewCell *)[[sender superview] superview]];
Thats it, if its more nested, add in more superviews.
Bit more info:
all you are doing is asking for the parent view and its parent view which is the cell. Then you are asking your tableview for the indexpath of that cell you just got.
One common way to do this is to set the tag of the control (in your case the switch) to something that can be used to identify the row or represented object.
For example, in tableView:cellForRowAtIndexPath: set the tag property of the switch to the indexPath.row and in your action method you can get the tag from the sender.
Personally, I don't like this approach and prefer subclassing UITableViewCell.
Also, it may be a good idea to add an "offset" to the tag to prevent any conflicts with the tags of other views.
The accepted answer on this post is perfectly fine. I'd like to suggest to readers that the following, derived from #robmayoff on this post, is also perfectly fine:
- (NSIndexPath *)indexPathForView:(UIView *)view inTableView:(UITableView *)tableView {
while (view && ![view isKindOfClass:[UITableViewCell class]])
view = view.superview;
UITableViewCell *cell = (UITableViewCell *)view;
return [tableView indexPathForCell:cell];
}
Some have asserted that this approach contains too much computational work because of the while loop. The alternative, convert the view's origin to table view coordinate space and call indexPathForRowAtPoint:, hides even more work.
Some have asserted that this approach is unsafe relative to potential SDK changes. In fact, Apple has already changed the tableview cell hierarchy once, adding a contentView to the cell. This approach works before and after such a change. As long as view ancestors can be found via a chain of superviews (which is as fundamental as anything in UIKit), this is good code.
A colleague suggested the following, which I made into a UITableView category:
+(UITableViewCell*)findParentCellForSubview:(UIView*)view
{
while (([view isKindOfClass:[UITableViewCell class]] == NO) && ([view superview] != nil))
view = [view superview];
if ([view superview] != nil)
return (UITableViewCell*)view;
return nil;
}
Still hackly - but it works.
One more variant of using superView. Works like category for UIView.
- (UITableViewCell *)superCell
{
if (!self.superview) {
return nil;
}
if ([self.superview isKindOfClass:[UITableViewCell class]]) {
return (UITableViewCell *)self.superview;
}
return [self.superview superCell];
}
i dont know about the multiple sections but i can give you for the one section...
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSInteger index=indexPath.row;
NSString *string=[[NSString alloc]initWithFormat:#"%ld",(long)index];
}
from this you can get the row number and you can save it to the string....

How can a UITableViewCell know of its own indexPath?

The standard Grouped UITableView style allows UITableViewCells to be drawn with rounded corners at the top and bottom of each section. How is this accomplished? How does the cell know its own location within its section, and how does it know when to change its rounded edges?
I want to make my own rounded cells, and I have images to use, but don't know when to show which image
Note: I already know how the UITableView works, and I know how to use it. I just thought that since a UITableView is able to automatically draw rounded corners at the correct places, I should be able to as well, without needing to add anything to my data source or delegate.
NSIndexPath *indexPath = [(UITableView *)self.superview indexPathForCell: self];
int rows = [(UITableView *)self.superview numberOfRowsInSection:indexPath.section];
if (indexPath.row == 0 && rows == 1) {
// the one and only cell in the section
}
else if (indexPath.row == 0) {
//top
}
else if (indexPath.row != rows - 1) {
//middle
}
else {
//bottom
}
It's very simple. suppose cell is the object, whose position is to be found out.
UITableView* table = (UITableView *)[cell superview];
NSIndexPath* pathOfTheCell = [table indexPathForCell:cell];
NSInteger sectionOfTheCell = [pathOfTheCell section];
NSInteger rowOfTheCell = [pathOfTheCell row];
There is sectionLocation method of UITableViewCell that returns integer telling you what you need:
1 - middle cell
2 - top cell
3 - bottom cell
4 - single cell
I had no issues using this in several production apps since 2010.
UPDATE: one of our binaries was automatically rejected recently (end of 2018) because we were using 'sectionLocation' property, so it's not a good option anymore.
Add something like this into your header files and you can use it:
typedef NS_ENUM(NSInteger, MMMTableViewCellLocation) {
MMMTableViewCellLocationUndefined = 0,
MMMTableViewCellLocationMiddle = 1,
MMMTableViewCellLocationTop = 2,
MMMTableViewCellLocationBottom = 3,
MMMTableViewCellLocationSingle = 4
};
#interface UITableViewCell ()
/** Undocumented method of UITableViewCell which allows to know where within section the cell is located,
* so the cell can draw its borders properly. */
- (MMMTableViewCellLocation)sectionLocation;
/** Override this one to know when the value of sectionLocation changes. */
- (void)setSectionLocation:(MMMTableViewCellLocation)sectionLocation animated:(BOOL)animated;
#end
You can use
- (NSIndexPath *)indexPathForCell:(UITableViewCell *)cell
for this issue. In my example I am using this to scroll the cell (with custom content) to the top of the view.
If you need more robust and general stuff, take a look at http://cocoawithlove.com/2009/04/easy-custom-uitableview-drawing.html - Matt Gallagher shows what you need, pretty effectively. He basically recreates UITableViewController from UIViewController, while adding ability to use your own custom graphics. I'm just working on applying this to one my projects, so far it looks it would do the job.
Unfortunately, I have found no solution to this problem, and have resorted to subclassing UITableViewController and UITableViewCell into a generic solution that I can extend as necessary.
You don't do this in cell. Rounded corners are drawn in [tableView viewForHeaderInSection] and viewForFooterInSection.
The way I do it is to use Plain tableview style, then use these two views for roundness and cells are normal, no rounds.
Without getting into who draws what, you can know which cell is the last cell in its section inside of cellForRowAtIndexPath very easily.
You're passed in the indexPath of the cell you need to provide, right? You're also passed the tableView.
call [tableView numberofRowsInSection:indexPath.section] and if it's == ([indexPath.row]-1) you know you're being asked to supply the last cell in that section.
At the time that cellForRowAtIndexPath is being called, the cell is guaranteed to be at the indexPath passed in.
To expand upon Darren's answer (which I found most useful, thanks Darren!), what you can do is to iterate through all of the superviews' until you find the parent UITableView. This should be future proof since you do not rely on a fixed hierarchy of views.
I use a recursive method that will return the UITableView if it finds one or return nil if there is none.
- (UITableView *)parentTableViewOf:(UIView *)view {
Class class = [view.superview class];
NSLog(#"Class : %#", NSStringFromClass(class));
if([view.superview isKindOfClass:[UITableView class]]) {
return (UITableView *)view.superview;
} else {
return [self parentTableViewOf:view.superview];
}
return nil;
}
So far I've used this one and it seems to work without hiccups. Hope it helps! :)
The cells dont know where they go...The table view has cells, You are the one telling the table view WHAT goes in the cell. You do this in the DataSource where you implement cellForRowAtIndexPath...The way this works :
An index path has a row and a section
For a grouped table view
A section pertains to a group, and a row pertains to 1 entry in that section,
the way UITableView knows how many rows are in a section and how many sections there are is the DataSources methods numberOfSectionInTableView and the method numberOfRowsInSection, this will make the right calls to cellForRowAtIndexPath, it is up to you to recognize which section and row is being queried and you need to build your cell according to these specifications.
A good way to do this i s you can have a Dictionary with keys of section names and values of NSArray with the values that go in that section.
So you implementation for numberOfSectionsInRows would look like
return [[dictionary allKeys] count]
And the implmentation of numberOfRowsInSection would look like
NSString* key=[[dictionary allKeys] objectAtIndex:sectionNumber]
return [[dictionary objectForKey:key] count]
You can always refer to the UITableView programming guide at http://developer.apple.com/iphone/library/documentation/UserExperience/Conceptual/TableView_iPhone/Introduction/Introduction.html
Hope that helps
Simply add a property to your custom UITableViewCell (depending on implementation) class that contains an int, NSNumber, or an NSIndexPath specifying which one it is. In you're using a data structure instead, then put it in you element in that data structure. Then you simply set the property when you create the data structure, something like elt.id=i, and then you access it in the cellForRowAtIndexPath, something like if (elt.id == 0 || elt.id == n-1) where n is the number of rows in your section.
I might have totally missed your question, but if I did, just comment and I'll post again.