So I have a detail view that need to display information by getting a index integer caculated from the indexpath from the selected cell in self.tableview, I've been using the NSFetchedResultsController for the self.tableview too. I've also implemented a UISearchDisplayController to do search.
Question:
How do I convert the selected indexPath in the UISearchDisplayController tableview to the indexpath of the original self.tableview? Or do I need to set up a NSArray instance and loop through it to find out the index? What's the most efficient way to do it?
Here is the code:
-(void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
NSLog(#"display project details");
if (tableView == self.table) {
[parentController projectSelectedFromList:indexPath.row];
}else{
NSInteger index;
//what to put here? To get the indexPath of the self.table from search tableview
[parentController projectSelectedFromList:index];
}
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
Assuming no duplicates,
id selectedObject = [searchArray objectAtIndex:indexPath.row];
index = [sourceArray indexOfObject:selectedObject];
Idea is pretty simple. Get the selected object of your search array as it will map directly to the index path's row. Then search for the object's index within the source or master array. This should give you the index you want.
Look at what you have you got in tableView:cellForRowAtIndexPath: of your tableView data source (usually the tableViewController object). Assuming you do a similar check on whether you are using the standard tableView or the search tableView there - and if you're not, then you're search filter surely won't be effective - you just need to have analogous code in the your tableView:didSelectRowAtIndexPath:.
If I'm missing the point here then apologies... and you might need to say a bit more in your question.
I found out the solution:
-(void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
NSLog(#"display project details");
if (tableView == self.table) {
[parentController projectSelectedFromList:indexPath.row];
NSLog(#"indexpath at orignal tableview is: %#", [indexPath description]);
}else{
NSIndexPath *indexPathForOriginal = [resultsController indexPathForObject: [self.filteredResults objectAtIndex:indexPath.row]];
NSInteger index = indexPathForOriginal.row;
[parentController projectSelectedFromList:index];
NSLog(#"indexpath at search tableview is: %#", [indexPathForOriginal description]);
}
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
Swift version of #Deepak Danduprolu answer:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
var selectedObject = searchedArray[indexPath.row]
var index = OrignalArray.index(of: selectedObject)
print("index", index) // This will give you index of selected array from the original array
}
Related
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
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.
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⬆️
After quite a long time perusing the web i have decided to come here as i usually have my questions answered super speedy and super well on Stack Overflow!
I am attempting to complete the following and would be grateful of any suggestions or pointers in the right direction:
Display a UITableView
When a "+" button is pressed a UITextField is added to a row of the UITableView
The user can type a string into this field
If the user selects the "+" Button again another UITextfield is added to the next row. (You get the idea)
When the user is done adding UITextFields and filling them in they can hit a "Save" button at which point all entries in each row are saved to an array.
Any suggestions regarding this would be greatly appreciated
Thanks!
Tom
This pseudocode might be a starting point:
// define an NSMutableArray called fields
- (IBAction)addField:(id)sender {
// create a new text field here and add it to the array
// then reload data
}
- (IBAction)save:(id)sender {
for(UITextField *textField in fields) {
// whatever you have to do
}
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// you have as many rows as textfields
return [fields 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];
}
// Set up the cell...
// For row N, you add the textfield N to the array.
[cell addSubview:(UITextField *)[fields objectAtIndex:indexPath.row]];
}
UPDATE FOR SWIFT 4
Instead of reloading the whole table you can use beginUpdate and endUpdate here.
Add target to the cell button(You can use view tag or create new TableviewCell class).
cell.addBtn.tag = indexPath.row
cell.addBtn.addTarget(self, action: #selector(self.addTextField(_:)), for: UIControlEvents.touchUpInside)
cell.txtField.tag = indexPath.row
Then implement this method:
#objc func addTextField(_ sender:UIButton) {
let indexPath = IndexPath(row: sender.tag, section: 0)
if let cell = tblView.cellForRow(at: indexPath) as? TableCell {
tblArray.append(cell.txtField.text!)
}
let newRow = IndexPath(row: sender.tag + 1, section: 0)
tblView.beginUpdates()
tblView.insertRows(at: [newRow], with: .left)
tblView.endUpdates()
}
Do not forget to put var tblArray:[Any] = [""] at start.
Hope this anyone who is looking for Swift answer.
Happy coding.
In a lot of iPhone apps, I see a UITableViewController being used as a checkbox list. (See, for an example of what I mean, Auto-Lock under Settings)
While trying to implement this myself, I had to jump through a lot of hoops in order to have an item selected programmatically by default (ie., the current value for what the list represents). The best I've been able to come up with is by overriding the viewDidAppear method in my view controller class:
- (void)viewDidAppear:(BOOL)animated {
NSInteger row = 0;
// loop through my list of items to determine the row matching the current setting
for (NSString *item in statusItems) {
if ([item isEqualToString:currentStatus]) {
break;
}
++row;
}
// fetch the array of visible cells, get cell matching my row and set the
// accessory type
NSArray *arr = [self.tableView visibleCells];
NSIndexPath *ip = [self.tableView indexPathForCell:[arr objectAtIndex:row]];
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:ip];
cell.accessoryType = UITableViewCellAccessoryCheckmark;
self.lastIndexPath = ip;
[super viewDidAppear:animated];
}
Is this the best/only/easiest way to get a reference to a particular cell and indexPath if I want to mark a row by default?
In order to display the status items, you have to implement tableView:cellForRowAtIndexPath: anyway, don't you? So, why not just set the accessory type of the cell before returning the cell, like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// dequeue or create cell as usual
// get the status item (assuming you have a statusItems array, which appears in your sample code)
NSString* statusItem = [statusItems objectAtIndex:indexPath.row];
cell.text = statusItem;
// set the appropriate accessory type
if([statusItem isEqualToString:currentStatus]) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
else {
cell.accessoryType = UITableViewCellAccessoryNone;
}
return cell;
}
Your code is extremely fragile, especially because you use [self.tableView visibleCells]. What if there are more status items than rows fitting on the screen (as the name suggests, visibleCells only returns the currently visible cells of the table view)?