UITableViewCell expand on click - iphone

Lets say we have a custom UITableViewCell
So whenever I click custom button on cell.. it should expand to the some extent (you can say 40 height more...) and when i click again to the same custom button it should collapse to the previous height.
Developer's please guide me.. how can I achieve this task

I'm not going to say anything here to contradict the accepted answer considering it is perfectly correct. However, I am going to go into more detail on how to accomplish this. If you don't want to read through all this and are more interested in playing with the source code in a working project, I've uploaded an example project to GitHub.
The basic idea is is to have a condition inside of the method -tableView: heightForRowAtIndexPath: that determines whether or not the current cell should be expanded. This will be triggered by calling begin/end updates on the table from within -tableView: didSelectRowAtIndexPath: In this example, I'll show how to make a table view that allows for one cell to be expanded at a time.
The first thing that you'll need to do is declare a reference to an NSIndexPath object. You can do this however you want, but I recommend using a property declaration like this:
#property (strong, nonatomic) NSIndexPath *expandedIndexPath;
NOTE: You do not need to create this index path inside viewDidLoad, or any other similar method. The fact that the index is initially nil will only mean that the table will not initially have an expanded row. If you would rather the table start off with a row of your choice expanded, you could add something like this to your viewDidLoad method:
NSInteger row = 1;
NSInteger section = 2;
self.expandedIndexPath = [NSIndexPath indexPathForRow:row inSection:section];
The next step is to head on over to your UITableViewDelegate method -tableView: didSelectRowAtIndexPath: to add the logic to alter the expanded cell index based on the users selection. The idea here is to check the index path that has just been selected against the index path stored inside the expandedIndexPath variable. If the two are a match, then we know that the user is trying to deselect the expanded cell in which case, we set the variable to nil. Otherwise, we set the expandedIndexPath variable to the index that was just selected. This is all done between calls to beginUpdates/endUpdates, to allow the table view to automatically handle the transition animation.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView beginUpdates]; // tell the table you're about to start making changes
// If the index path of the currently expanded cell is the same as the index that
// has just been tapped set the expanded index to nil so that there aren't any
// expanded cells, otherwise, set the expanded index to the index that has just
// been selected.
if ([indexPath compare:self.expandedIndexPath] == NSOrderedSame) {
self.expandedIndexPath = nil;
} else {
self.expandedIndexPath = indexPath;
}
[tableView endUpdates]; // tell the table you're done making your changes
}
Then the final step is in another UITableViewDelegate method -tableView: heightForRowAtIndexPath:. This method will be called after you've triggered beginUpdates once for each index path that the table determines needs updating. This is where you'll compare the expandedIndexPath against the index path that is currently being reevaluated.
If the two index paths are the same, then this is the cell that you wish to be expanded, otherwise it's height should be normal. I used the values 100 and 44, but you can use what ever suits your needs.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Compares the index path for the current cell to the index path stored in the expanded
// index path variable. If the two match, return a height of 100 points, otherwise return
// a height of 44 points.
if ([indexPath compare:self.expandedIndexPath] == NSOrderedSame) {
return 100.0; // Expanded height
}
return 44.0; // Normal height
}

Implement heightForRowAtIndexPath to calculate the right height. Then in the code for your button, force the table to reevaluate each cell's height with beginUpdates plus endUpdates:
[self.tableView beginUpdates];
[self.tableView endUpdates];
Changes to the tableview cells' heights will automatically be calculated with heightForRowAtIndexPath and the changes will be animated too.
In fact, instead of a button on your cell that does this, you might even just make selecting the cell do this in didSelectRowAtIndexPath.

Instead of using [tableView beginUpdates] and [tableView endUpdates] , I'm using the [tableView reloadRowsAtIndexPath:... withRowAnimation:...] method inside the didSelectRowAtIndexPath method.
I prefer this, because I had some problems with elements that should show, when I expand my UITableViewCell, when I used the begin & end updates methods. Another point is that you can choose between some animations like: Top, Bottom, Left, Right...

I have created an open source library for this. You just implement collapse and expand delegates in your code and voilà! you can also perform any drawings and animations. check out this.

I've made a reusable component that will do exactly what you're talking about. It's pretty easy to use, and there's a demo project.
GCRetractableSectionController on GitHub.

This is Mick's answer but for Swift 4. (IndexPath replaces NSIndexPath, which comes with an empty IndexPath as nil would crash Swift. Also, you can compare two instances of IndexPath using ==)
Declare the expandedIndexPath property.
var expandedIndexPath = IndexPath()
Optional viewDidLoad part.
expandedIndexPath = IndexPath(row: 1, section: 2)
Then the didSelectRow part.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.beginUpdates()
if indexPath == expandedIndexPath {
expandedIndexPath = IndexPath()
} else {
expandedIndexPath = indexPath
}
tableView.endUpdates()
}
Then the heightForRow part.
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath == expandedIndexPath {
return 100
}
return 44
}

I used Gcamp's source code and made my own version.
1) In a loadView method initialize a mutable array where you will save expanded or non-expanded states of your sections. It is critical to save expanded statuses in a separate array, that is not destroyed while table view scrolls (for instance if you store it in a headerView it will be redrawn and forget weather it was expanded or not). In my case it is _sectionStatuses array.
- (void)loadView
{
// At the beginning all sections are expanded
_sectionStates = [NSMutableArray arrayWithCapacity:self.tableView.numberOfSections];
for (int i = 0; i < self.tableView.numberOfSections; i++) {
_sectionStates[i] = [NSNumber numberWithBool:YES];
}
}
2) Create a custom headerView for a section with a button for expanding. Delegate an action from a button in your headerView to your TableViewController using delegation pattern. You can find suitable images in Gcamp's source code.
3) Create an action to remove or add rows. Here _foldersArray is my structure, that contains all the data. My section's headerView - MCExpandableAccountHeaderView knows it's own section number - I transfer it there when I create header views for each section. It is critical to transfer it to this method, since you have to know which section is now expanded or stretched.
- (void)expandClicked:(MCAccountHeaderView *)sender
{
MCExpandableAccountHeaderView *expandableAccountHeaderView = (MCExpandableAccountHeaderView*)sender;
// Finding a section, where a button was tapped
NSInteger section = expandableAccountHeaderView.section;
// Number of rows, that must be in a section when it is expanded
NSUInteger contentCount = [_foldersArray[section - 1][#"folders"] count];
// Change a saved status of a section
BOOL expanded = [_sectionStates[section] boolValue];
expanded = ! expanded;
expandableAccountHeaderView.expanded = expanded;
_sectionStates[section] = [NSNumber numberWithBool:expanded];
// Animation in a table
[self.tableView beginUpdates];
NSMutableArray* modifiedIndexPaths = [[NSMutableArray alloc] init];
for (NSUInteger i = 0; i < contentCount; i++) {
NSIndexPath* indexPath = [NSIndexPath indexPathForRow:i inSection:section];
[modifiedIndexPaths addObject:indexPath];
}
if (expandableAccountHeaderView.expanded) [self.tableView insertRowsAtIndexPaths:modifiedIndexPaths withRowAnimation:UITableViewRowAnimationFade];
else [self.tableView deleteRowsAtIndexPaths:modifiedIndexPaths withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];
// Scroll to the top of current expanded section
if (expandableAccountHeaderView.expanded) [self.tableView scrollToRowAtIndexPath:INDEX_PATH(0, section) atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
4) It is also important to return correct number or rows in a section depending on wheather it is expanded or not.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
BOOL expanded = [_sectionStates[section] boolValue];
return expanded ? [_foldersArray[section - 1][#"folders"] count] : 0;
}

initialize iSelectedIndex = -1; and declare
UITableView *urTableView;
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
return 10; //Section count
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 3; //row count
}
- (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] autorelease];
}
[cell.textLabel setText:[NSString stringWithFormat:#"sec:%d,row:%d",indexPath.section,indexPath.row]];
return cell;
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section{
// adding a label with the tap gesture to the header in each section
headerLabel = [[UILabel alloc]init];
headerLabel.tag = section;
headerLabel.userInteractionEnabled = YES;
headerLabel.backgroundColor = [UIColor greenColor];
headerLabel.text = [NSString stringWithFormat:#"Header No.%d",section];
headerLabel.frame = CGRectMake(0, 0, tableView.tableHeaderView.frame.size.width, tableView.tableHeaderView.frame.size.height);
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(gestureTapped:)];
[headerLabel addGestureRecognizer:tapGesture];
return headerLabel;
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{
return 50.0; //adjust the height as you need
}
- (void)gestureTapped:(UITapGestureRecognizer *)sender{
UIView *theSuperview = self.view; // whatever view contains
CGPoint touchPointInSuperview = [sender locationInView:theSuperview];
UIView *touchedView = [theSuperview hitTest:touchPointInSuperview withEvent:nil];
if([touchedView isKindOfClass:[UILabel class]])
{
if (iSelectedIndex != touchedView.tag) { //if new header is selected , need to expand
iSelectedIndex = touchedView.tag;
}else{ // if the header is already expanded , need to collapse
iSelectedIndex = -1;
}
[urTableView beginUpdates];
[urTableView endUpdates];
}
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
// Show or hide cell
float height = 0.0;
if (indexPath.section == iSelectedIndex) {
height = 44.0; // Show the cell - adjust the height as you need
}
return height;
}

For me it works to use:
on UITableViewDelegate
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("Did select row: \(indexPath.row).")
tableView.beginUpdates()
tableView.endUpdates()
}
on selectable/expandable UITableViewCell
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
configStyle(selected)
}
Important! tableView.rowHeight is .automatic and UITableViewCell is constraint to enable automatic height calculation, i.e. its height constraint are clearly defined like constraint to top/bottom or height constraints added or label intrinsic content size is used.

To add to 0x7fffffff's answer, I found I needed an extra condition in the if statement within didSelectRowAtIndexPath - thus:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView beginUpdates];
if (self.expandedIndexPath && [indexPath compare:self.expandedIndexPath] == NSOrderedSame) {
self.expandedIndexPath = nil;
} else {
self.expandedIndexPath = indexPath;
}
[tableView endUpdates];
}

Following this medium article on how to expand the cells based on the tap of a button and setting the numbersOfLine for a specific label, I was able to perform the animation using
tableView.beginUpdates()
tableView.performBatchUpdates({
cell.description.numberOfLines = !expanded ? 0 : 3
}, completion: nil)
tableView.endUpdates()
Notice performBatchUpdates is only available in iOS 11⬆️

Related

When click on button (button in cell) that particular cell should be changed?

There are 1 table on uiview and I want to change the cell height when button pressed other cell's height remain same
Pass the button press event to the view controller through delegate methods and reload the table view as follows.
[self.tableView reloadData];
In view controller (ie., datasource for the table view), implement heightForRowAtIndexPath method and return the height as required.
You can change the cell height in the delegate method
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
if(indexPath.row == clickedRow)
return newHeitht;
return cellHeight;
}
you can set some condition in button click and reload the tableview using [tableView reloadData]. this function will be called. return a new height for the particular cell.
-(void)buttonClick {
[self.tableview reloadData];
selectedRow = //do something here
}
and in your UITableview Datasource
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath*)indexPath {
if(indexPath.row == selectedRow)
return selectedRowHeight;
else
return defaultHeight;
}
1) Create a custom UITableViewCell class.
2) Populate you cell however you see fit. I use a 'hydrateWithArray' type function myself.
3) Once the data has been populated, resize and reposition elements using 'sizeToFit' functions to force labels to conform to the size of whatever you put into them. protip: by setting the frame of the label first, and setting the numer of lines to 0... when you fill the text and sizetofit, it will stretch the label vertically only and force the width to stay the same.
4) Create a seperate function (mine is called calculatedHeight) that returns a float and returns the height that you would like the cell to be in the table (based on the repositioned objects from step 3).
- (float)calculatedHeight {
return textLabel.frame.origin.ytextLabel.frame.size.height5;
}
5) In your UITableView class, you'll need to import your tableViewCell class and create a dummy cell object. You're going to use this class to calculate how tall each cell needs to be. Then in the heightOfRowAtIndex method....
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
float height;
if ( !dummyCell ) dummyCell = [[CustomCell alloc] initWithFrame:CGRectMake(0,0,0,0) reuseIdentifier:#"myCell"];
[dummyCell hydrateWithTweet:[tableArray objectAtIndex:indexPath.row]];
height = [dummyCell calculatedHeight];
if ( height == 0 ) height = 50;
return height;
}
This is a pretty simple example so you may need to go crazy with the error checking in your particular use, but this should at least point you in the right direction. Enjoy!
(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if (isSearching && indexPath.row == selectedIndex) {
return 110;
}
else {
return rowHeight;
}
[self.tableView beginUpdates];
[self.tableView reloadRowsAtIndexPaths:toReloadRows withRowAnimation: UITableViewRowAnimationNone];
[self.tableView endUpdates];
then
[self.tableView beginUpdates];
[self.tableView endUpdates];

How to remove a Static Cell from a UITableView designed in StoryBoard

The solution is probably very simple, but I couldn't just find it .. !
Working with storyboard (iOS 5), I have a tableViewController, and a designed STATIC tableview with 5 sections, with differents static cell inside each section.
My question is: How to delete a cell programatically in the viewWillAppear?
For example, I have a cell designed for a date
IBOutlet UITableViewCell * cellForDate;
And.. if there's not date, I want to remove my cell.
cellForDate.hidden = true; //Hide the cell, but leave a blank space
Ive tried [tableView deleteRowsAtIndexPaths...] didn't work
Anyone got an idea?
Try to hide the cell before it is shown, in UITableViewDelegate's tableView:willDisplayCell:forRowAtIndexPath: method. That's the final method where you can manipulate the cell's appearance. This however won't remove the space the cell should take, so another thing you can try is to set cell row's height to 0 using the tableView:heightForRowAtIndexPath: method of the same protocol.
Another method, which is most robust, is to devise a method for determining whether you need the date cell in the section and depending on the result, return the proper number of rows in the section, and return other section row cells taking the situation into account. And then reload tableView's data on viewWillAppear.
Hopefully this is a bit more of a universal solution, but it's still not perfect
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [super tableView:tableView cellForRowAtIndexPath:indexPath];
if (cell.hidden) {
return 0;
} else {
return [super tableView:tableView heightForRowAtIndexPath:indexPath];
}
}
With this method you set your IBOutlet bound cell to be hidden, and then when the table tries to display a row, it first checks to see if the cell (using super call to get the cell) should be hidden, if so it returns a height of 0 which effectively removes it from the list. If the cell shouldn't be hidden then it gets the height from the super so whatever would normally happen still happens.
The only issue with this is that start and end cells are responsible for the divider at the top and bottom of the list or group. So if you hide the last cell in a group the bottom divider on the group will be inset slightly like it is on normal rows rather than full width. This is only a problem if you're hiding the top or bottom rows AND you are using a divider AND you care about totally standard display. The best solution in my case was simply to not hide the top or bottom rows. I moved any content that might be hidden to the middle of the group. Alternatively you could just accept that it isn't quite standard, or disable row dividers on your table.
Swift 3
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if indexPath.section == 0 {
cell.isHidden = true
}
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.section == 0 {
return 0
} else {
return super.tableView(tableView, heightForRowAt: indexPath)
}
}
If you want to permanently delete the tableview cell, just call deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationNone] with the an indexPath corresponding to your row. You will also need to decrement the number of rows returned by numberOfRowsInSection:.
However, I was looking for a way to temporarily "delete" unwanted static tableview cells from a static tableview, because I wanted a particular row to be there sometimes, and other times not. I also wanted to be able to update this on demand from outside of the tableview controller.
My case is fairly simple, since it is the first row that was being either shown or hidden. You can generalize to suit your own needs. My tableview controller is my data source delegate.
First, I created a public method in the tableview controller to update the state variables and trigger the redisplay:
- (void)updateSettingsDisplay:(BOOL)hideSetting
{
if (hideSetting == self.hideSetting) {
return; // no need to do anything because it didn't change
}
self.hideSetting = hideSetting;
self.numRows = (hideSetting)? kNumRowsWithSetting : kNumRowsWithoutSetting;
// redisplay only if we're visible
if (!self.viewJustLoaded && (self.navController.visibleViewController == self)) {
[self.tableView reloadData];
}
self.viewJustLoaded = NO;
}
The tableview controller's viewDidLoad looks like:
- (void)viewDidLoad
{
[super viewDidLoad];
// check whether or not to display out-of-coverage tableview cell
self.hideSetting = YES; // initial value; could just as easily be NO
self.viewJustLoaded = YES;
[self updateSettingsDisplay];
}
The tableview's data-source delegate:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return self.numRows;
}
and finally
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// set row to the index of the stored tableView cell that corresponds to the
// cell you want (its location in the static tableview from your storyboard)
NSInteger row = indexPath.row;
if (self.hideSetting) {
// skip drawing setting's tableviewcell (since the setting we're hiding is
// first, we can just increment the row to get the one we want)
++row;
assert(row < kTotalRows); // bounds checking just to convince yourself (remove after testing)
}
// get a new index path since the row field is read-only
NSIndexPath *newIndexPath = [NSIndexPath indexPathForRow:row inSection:indexPath.section];
// grab the cell from super that corresponds to the cell you want
UITableViewCell *cell = [super tableView:tableView cellForRowAtIndexPath:newIndexPath]; // static table
return cell;
}
The trick is that the static cells are always available in [super tableView:tableView cellForRowAtIndexPath:newIndexPath] - so use that for permanent storage of your static tableview cells. Then adjust the number of rows as needed, and map the rows correctly (ie, get the row of the stored cell that corresponds to the cell you want displayed) in your tableview delegate's cellForRowAtIndexPath:.
The updateSettingsDisplay method can be called on your tableview controller by any class that retains it. If the tableview controller is not visible when it is called, it will just update the state and wait until next time it becomes visible to change the display.
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if(indexPath.section == 1) { //For example
return 0
}
}
override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
if(indexPath.section == 1) {
cell.hidden = true
}
}
you might want to look at this
https://github.com/xelvenone/StaticDataTableViewController/
usage
self.hideSectionsWithHiddenRows = YES; //YES, NO
[self cell:self.outletToMyStaticCell1 setHidden:hide];
[self cell:self.outletToMyStaticCell2 setHidden:hide];
[self reloadDataAnimated:YES];
FYI (would comment if I could), if you are overriding a Grouped static UITableView from the Storyboard, then you'll also need to override the Header & Footer views & heights for each section you are modifying/hiding.
I also needed to set the return value for the "GetHeight" methods to something other than zero - implying that a zero value means something else (autolayout, or "not set" or the like).
The below code is in Xamarin iOS C#, but translates directly to the appropriate Obj-C methods:
public override void WillDisplayHeaderView(UITableView tableView, UIView headerView, nint section)
{
if (section == 0)
{
headerView = new UIView(new CGRect(0,0,0,0));
}
}
public override nfloat GetHeightForHeader(UITableView tableView, nint section)
{
if (section == 0)
{
return 1f;
}
return base.GetHeightForHeader(tableView, section);
}
public override void WillDisplayFooterView(UITableView tableView, UIView footerView, nint section)
{
if (section == 0)
{
footerView = new UIView(new CGRect(0, 0, 0, 0));
}
}
public override nfloat GetHeightForFooter(UITableView tableView, nint section)
{
if (section == 0)
{
return 1f;
}
return base.GetHeightForFooter(tableView, section);
}
Just calculate the numberOfRows(inSection) to remove some rows you don't want
You need to put beginUpdates before the delete call and endUpdates after:
[tableView beginUpdates];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:r inSection:s]]];
[tableView endUpdates];
In response to your comment:
- (NSInteger)tableView:(UITableView)tableView numberOfRowsInSection:(NSInteger)section {
NSInteger ret;
switch (section) {
case 0:
// sectionZeroRows doesnt have to be a
// property (an attribute works just fine), but it
// helps with explaining this example.
ret = self.sectionZeroRows;
break;
// ...
}
return ret;
}
- (void)someMethod {
--self.sectionZeroRows;
[tableView beginUpdates];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:0 inSection:0]]];
[tableView endUpdates];
}
// just authed with facebook, and I want to delete the first 3 rows of my 4 row table that are no longer necessary
- (NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section {
if(wantsFacebook)
return 1;
return 4;
}
After committing the updates, the tableView will call the numberOfRowsInSection delegate to get the updated number of rows and it MUST be the correct sum/difference between the rows when you started the insert/delete and the rows when you ended the insert/delete

How to scroll UITableView to specific position

How can I scroll the table's cell to specific position ? I have a table which shows 3 rows (according to height). what I want is if I click on 1st row than according to table's height the 1st row should scroll and get new position (center) and same for other rows. I tried contenOffset but did not work..
EDITED :
In short some thing like data picker when we select any row in picker the row scrolls to the center.
Thanks..
it should work using - (void)scrollToRowAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UITableViewScrollPosition)scrollPosition animated:(BOOL)animated using it this way:
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
[yourTableView scrollToRowAtIndexPath:indexPath
atScrollPosition:UITableViewScrollPositionTop
animated:YES];
atScrollPosition could take any of these values:
typedef enum {
UITableViewScrollPositionNone,
UITableViewScrollPositionTop,
UITableViewScrollPositionMiddle,
UITableViewScrollPositionBottom
} UITableViewScrollPosition;
Swift 4.2 version:
let indexPath:IndexPath = IndexPath(row: 0, section: 0)
self.tableView.scrollToRow(at: indexPath, at: .none, animated: true)
Enum: These are the available tableView scroll positions - here for reference. You don't need to include this section in your code.
public enum UITableViewScrollPosition : Int {
case None
case Top
case Middle
case Bottom
}
DidSelectRow:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let theCell:UITableViewCell? = tableView.cellForRowAtIndexPath(indexPath)
if let theCell = theCell {
var tableViewCenter:CGPoint = tableView.contentOffset
tableViewCenter.y += tableView.frame.size.height/2
tableView.contentOffset = CGPointMake(0, theCell.center.y-65)
tableView.reloadData()
}
}
[tableview scrollRectToVisible:CGRectMake(0, 0, 1, 1) animated:NO];
This will take your tableview to the first row.
finally I found... it will work nice when table displays only 3 rows... if rows are more change should be accordingly...
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
// Customize the number of rows in the table view.
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section
{
return 30;
}
- (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] autorelease];
}
// Configure the cell.
cell.textLabel.text =[NSString stringWithFormat:#"Hello roe no. %d",[indexPath row]];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell * theCell = (UITableViewCell *)[tableView
cellForRowAtIndexPath:indexPath];
CGPoint tableViewCenter = [tableView contentOffset];
tableViewCenter.y += myTable.frame.size.height/2;
[tableView setContentOffset:CGPointMake(0,theCell.center.y-65) animated:YES];
[tableView reloadData];
}
Use [tableView scrollToRowAtIndexPath:indexPath atScrollPosition:scrollPosition animated:YES];
Scrolls the receiver until a row identified by index path is at a particular location on the screen.
And
scrollToNearestSelectedRowAtScrollPosition:animated:
Scrolls the table view so that the selected row nearest to a specified position in the table view is at that position.
It is worth noting that if you use the setContentOffset approach, it may cause your table view/collection view to jump a little. I would honestly try to go about this another way. A recommendation is to use the scroll view delegate methods you are given for free.
Simply single line of code:
self.tblViewMessages.scrollToRow(at: IndexPath.init(row: arrayChat.count-1, section: 0), at: .bottom, animated: isAnimeted)
In case, your table view only has one row and one section, you also can use following approach
challengeInfoTableView.scrollRectToVisible(challengeInfoCell.challengeDescriptionTextView!.frame, animated: false)
In this case, you can directly scroll to the correct text field, text view, label etc.

How to uncheck all rows using UITableViewCellAccessoryCheckmark

I've got a UITableView with each row containing a checkbox using UITableViewCellAccessoryCheckmark. I can't figure out how to uncheck all the checkboxes using the didSelectRowAtIndexPath method.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *oldCell;
int count = [self.myTableRowNamesArray count];
for (NSUInteger i = 0; i < count; ++i) {
// Uncheck all checkboxes
// OF COURSE THIS DOESN'T WORK
// BECAUSE i IS AN INTEGER AND INDEXPATH IS A POINTER
FOO: oldCell = [myTableView cellForRowAtIndexPath:(int)i];
// GOOD CODE:
oldCell = [penanceOptionsTableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]];
oldCell.accessoryType = UITableViewCellAccessoryNone;
}
UITableViewCell *newCell = [myTableView cellForRowAtIndexPath:indexPath];
newCell.accessoryType = UITableViewCellAccessoryCheckmark;
}
Instead of modifying the .accessoryType of all cells in didSelectRowAtIndexPath:, I suggest storing the selected index in some ivar, and change the .accessoryType in the data source's -tableView:cellForRowAtIndexPath: method, i.e.
-(void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
self.selectedIndexPath = indexPath;
[tableView reloadData];
}
-(UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath {
...
cell.accessoryType = [indexPath compare:self.selectedIndexPath] == NSOrderedSame
? UITableViewCellAccessoryCheckmark
: UITableViewCellAccessoryNone;
...
}
With this, only visible cells will be affected, and the million other cells outside of the screen won't need to be modified.
Quite right, here's a full implementation in Swift in the general case of selecting a cell .. you'd use selectedIndexPath elsewhere in the class as you see fit. For example, in cellForRowAtIndexPath to choose the appropriate cell prototype.
// SelectingTableViewController
import UIKit
class SelectingTableViewController: UITableViewController
{
internal var selectedIndexPath:NSIndexPath? = nil
override func viewDidLoad()
{
super.viewDidLoad()
tableView.estimatedRowHeight = 68.0
tableView.rowHeight = UITableViewAutomaticDimension
self.clearsSelectionOnViewWillAppear = false;
}
override func tableView
(tableView:UITableView, didSelectRowAtIndexPath indexPath:NSIndexPath)
{
print("did select....")
// in fact, was this very row selected,
// and the user is clicking to deselect it...
// if you don't want "click a selected row to deselect"
// then on't include this clause.
if selectedIndexPath == indexPath
{
print("(user clicked on selected to deselect)")
selectedIndexPath = nil
tableView.reloadRowsAtIndexPaths(
[indexPath],
withRowAnimation:UITableViewRowAnimation.None)
tableView.deselectRowAtIndexPath(indexPath, animated:false)
return
}
// in fact, was some other row selected??
// user is changing to this row? if so, also deselect that row
if selectedIndexPath != nil
{
let pleaseRedrawMe = selectedIndexPath!
// (note that it will be drawn un-selected
// since we're chaging the 'selectedIndexPath' global)
selectedIndexPath = indexPath
tableView.reloadRowsAtIndexPaths(
[pleaseRedrawMe, indexPath],
withRowAnimation:UITableViewRowAnimation.None)
return;
}
// no previous selection.
// simply select that new one the user just touched.
// note that you can not use Apple's willDeselectRowAtIndexPath
// functions ... because they are freaky
selectedIndexPath = indexPath
tableView.reloadRowsAtIndexPaths(
[indexPath],
withRowAnimation:UITableViewRowAnimation.None)
}
}
for (UITableViewCell *cell in [myTableView visibleCells]) {
cell.accessoryType = UITableViewCellAccessoryNone;
}
But really, you'd be better off just modifying the one cell that actually has the checkmark set. You have to have stored this information somewhere in your model anyway.
You're probably setting some kind of property with this method.
So what i do is:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// 1. first unsetting the property
[object someProperty:nil];
// 2. call the reloadData method to uncheck all the checkmarks
[tableView reloadData];
// 3. check the selected cell
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
[cell setAccessoryType:UITableViewCellAccessoryCheckmark];
// 4. set the checked property
[object setSomeProperty:[indexpath row]];
}
And in my cellForRowAtIndexPath methods i got something like the following code:
if([object someProperty] == [indexpath row]){
[cell setAccessoryType:UITableViewCellAccessoryCheckmark];
} else {
[cell setAccessoryType:UITableViewCellAccessoryNone];
}
Yes, cellForRowAtIndexPath: uses NSIndexPath instead of integer so make indexpath by using
indexPathForRow:inSection:
if you are using one section then your loop is fine just pass i in row and 0 for section.

UITableView Multiple Selection

How can I add a UITableView into my View-Based Application where the user will tap on more than one cell, and it will become selected, exactly like the Clock app's "New Alarm" setting named "Repeat" (Clock>Alarms> + >Repeat), and how can I get all of the selected cells in an array?
For multiple selection, add the line below in viewDidLoad()
tableView.allowsMultipleSelection = true
Configure each cell after dequeuing (or initializing) it in tableView(_:cellForRowAt:)
let selectedIndexPaths = tableView.indexPathsForSelectedRows
let rowIsSelected = selectedIndexPaths != nil && selectedIndexPaths!.contains(indexPath)
cell.accessoryType = rowIsSelected ? .checkmark : .none
// cell.accessoryView.hidden = !rowIsSelected // if using a custom image
Update each cell when it's selected/deselected
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath)!
cell.accessoryType = .checkmark
// cell.accessoryView.hidden = false // if using a custom image
}
override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath)!
cell.accessoryType = .none
// cell.accessoryView.hidden = true // if using a custom image
}
When you're done, get an array of all the selected rows
let selectedRows = tableView.indexPathsForSelectedRows
and get the selected data, where dataArray maps to the rows of a table view with only 1 section
let selectedData = selectedRows?.map { dataArray[$0.row].ID }
In your implementation of -tableView:didSelectRowAtIndexPath: you would set the table view cell's accessoryType property depending on its current value (so it would toggle on and off with multiple taps). For example:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)path {
UITableViewCell *cell = [tableView cellForRowAtIndexPath:path];
if (cell.accessoryType == UITableViewCellAccessoryCheckmark) {
cell.accessoryType = UITableViewCellAccessoryNone;
} else {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
}
You could either maintain an array of selected states in addition to the cells' own accessory type state, or iterate over the cells in the table view querying for each one's state in order to read out the selected rows.
#BrendanBreg implementation didn't worked for me. #RaphaelOliveira provided good solution, but when you scrolls your table down - wrong rows become selected (because UITableView caches it's cells). So, I've slightly modified Raphael's solution:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell* cell = [tableView cellForRowAtIndexPath:indexPath];
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell* cell = [tableView cellForRowAtIndexPath:indexPath];
cell.accessoryType = UITableViewCellAccessoryNone;
}
/*Here is modified part*/
- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
/*
...
Your implementation stays here
we're just adding few lines to make sure
that only correct rows will be selected
*/
if([[tableView indexPathsForSelectedRows] containsObject:indexPath]) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
} else {
cell.accessoryType = UITableViewCellAccessoryNone;
}
}
self.tableView.allowsMultipleSelection = YES;
Just a quick tip in addition to the great answer above: to mimic Apple's style from the clock app (making the row select color fade back out after checking / unchecking the row), add this to the didSelectRowAtIndexPath, after the conditionals:
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
From Apple's TableMultiSelect guide.
Here it is what you needed
http://developer.apple.com/library/ios/#samplecode/TableMultiSelect/Introduction/Intro.html#//apple_ref/doc/uid/DTS40011189-Intro-DontLinkElementID_2
I have seen this problem with so many developers. Due to table view's nature of re-using cell it removes or haphazard the checks. I have created a working solution for that. Clone/download code from DevelopmentSupportWorkspace and Execute UITableViewTest project from there.
Here is code summery for that:
#interface CheckBoxTestTableViewController ()
#property (nonatomic, strong) NSArray *dataArray;
#property (nonatomic, strong) NSMutableDictionary *selectedIndexDictionary;
#end
#implementation CheckBoxTestTableViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Uncomment the following line to preserve selection between presentations.
// self.clearsSelectionOnViewWillAppear = NO;
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
//
_dataArray = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"ImageList" ofType:#"plist"]];
_selectedIndexDictionary = [NSMutableDictionary dictionary];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return _dataArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"checkMarkCell" forIndexPath:indexPath];
cell.accessoryType = UITableViewCellAccessoryNone;
// Configure the cell...
cell.textLabel.text = _dataArray[indexPath.row][#"text"];
if (_selectedIndexDictionary[indexPath] != nil) cell.accessoryType = UITableViewCellAccessoryCheckmark;
return cell;
}
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(nonnull NSIndexPath *)indexPath {
if (_selectedIndexDictionary[indexPath] == nil) {
[_selectedIndexDictionary setObject:_dataArray[indexPath.row] forKey:indexPath];
[[tableView cellForRowAtIndexPath:indexPath] setAccessoryType:UITableViewCellAccessoryCheckmark];
}else{
[_selectedIndexDictionary removeObjectForKey:indexPath];
[[tableView cellForRowAtIndexPath:indexPath] setAccessoryType:UITableViewCellAccessoryNone];
}
// [tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
#end
The clock alarms repeat table view is not multiple selection. Think of it as a lit of checkboxes.
When a cell is selected, the font color and accessory type are changed and the selection fades out. When a checked cell is selected, the font color and accessory type are changed back and the selection fades out.
In your didSelectRowAtIndexPath delegate method, you would set the text color and accessory type for the selected cell, then deselect the cell. You would also record the new state in your data model. That could be as simple as a bit mask representing the selected state, but depends on what data you are displaying.
In your cellForRowAtIndexPath: dataSource method, set the text color and accessory type based on your data model.
Actual multiple selection would be similar. You have to keep track of which cells are selected, and set the selected cell of each state as it is created or shown. When the table view reports that a cell is selected, toggle the selection state in your data model and set the selected state of the cell accordingly.
You can't key off indexPath because the cells that refers to changes as you scoll. Put an NSLog in cellForRowAtIndexPath to see that. You can do the check/uncheck in willSelectRowAtIndexPath or didSelectRowAtIndexPath. That covers only the initial check or uncheck though, and will also have things appear as checked once you've scrolled because the underlying cell for a given indexPath changes.
So the solution I found is to have an array of selected things with something that is specific to that given cell, and do the initial check.
- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell* cell = [tableView cellForRowAtIndexPath:indexPath];
if (![selectedIndexes containsObject:cell.textLabel.text]){
[selectedIndexes addObject:cell.textLabel.text];
cell.accessoryType = UITableViewCellAccessoryCheckmark;
} else {
[selectedIndexes removeObject:cell.textLabel.text];
cell.accessoryType = UITableViewCellAccessoryNone;
}
return indexPath;
}
You also have to have logic in cellForRowAtIndexPath to make sure the right stuff is checked or not as the view scrolls:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
...
cell.accessoryType = UITableViewCellAccessoryNone;
if ([selectedIndexes containsObject:cell.textLabel.text]) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
[cell setSelected:YES animated:YES];
}
return cell;
}
1 - Allow multiple selection and toggle of selected state:
tableView.allowsMultipleSelection = true
2 - Collect or get an array of all selected indices when you are done:
let selected = tableView.indexPathsForSelectedRows
Note: This is independent of which cells are currently showing on screen.
3 - Change the appearance of the cell depending on selected state: Override this method in your UITableViewCell subclass. If you don't have a subclass, make one.
// In UITableViewCell subclass
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
accessoryType = selected ? .Checkmark : .None
}
I know that this is an old posting, but for future use, the following code will solve the problem of ensuring that a checkmark will only appear on selected rows during scrolling:
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cell.accessoryType = (cell.isSelected) ? .checkmark : .none
}