I want to expand UITableView section where I have to show UITableViewCells. What should I do to achieve this?
A simple implementation would be keep your cell height as zero for
sections.
Make the viewForSectionHeader touchable
When you touch it, set proper height for cells under the section
Write a logic for switching between sections
OR,
On touching the section header reload the table with updated rows count for section touched.
Many other ways to do it. Apple example.
According to Vignesh's answer, I have tried the second solution."On touching the section header reload the table with updated rows count for section touched."
First, declare an array to store the isExpanded tag for each section.Initial, all value is BOOL NO.Means all the section rows are collapsed.
Then, in the
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
method, implement the sectionHeader touch event by following code:
Because I use a custom cell as section header view. so here is the "cell",you can use your own view.
cell.tag=section;
UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(sectionTapped:)];
[cell addGestureRecognizer:recognizer];
cell.userInteractionEnabled=YES;
And, do something when the sectionHeader is tapped.
- (void)sectionTapped:(UITapGestureRecognizer *)recognizer
{
NSMutableArray *isSectionTouched=[[NSMutableArray alloc]initWithCapacity:_sectionExpandBool.count];
isSectionTouched=[_sectionExpandBool mutableCopy];
if ([[isSectionTouched objectAtIndex:recognizer.view.tag]boolValue]==YES) {
[isSectionTouched replaceObjectAtIndex:recognizer.view.tag withObject:[NSNumber numberWithBool:NO]];
}else if ([[isSectionTouched objectAtIndex:recognizer.view.tag]boolValue]==NO){
[isSectionTouched replaceObjectAtIndex:recognizer.view.tag withObject:[NSNumber numberWithBool:YES]];
}
_sectionExpandBool=isSectionTouched;
[self.tableView reloadData];
}
Don't forget to modify the numberOfRowsInSection method. The row.count should change according the value of _sectionExpandBool, if the section's ExpandBool is YES, should return the correct number of your datasource,else return 0.
It works as my expectation. However, I'm a little worried about the memory leak or something, because every time tap the header, the whole table view will reload again.
I wonder if there is some solution for only reload the specific section. Thanks.
Related
I've got a project (that has been written by other people) where there's a feed with content and text displayed inside a table view. Each post corresponds to one section in table view, and each section has its own rows corresponding to elements like content, text, like button etc.
I need to display a short label for post captions with a "more" button inside table view cell, and when more button is tapped, the label will expand to whatever size the caption fits, all happening inside a table view cell. When the more button is tapped I change the label's numberOfLines property to zero, and as the cells have automatic height, all I need is to reload that particular caption cell. (the cell displays correctly with the expanded size if I set numberOfLines to 0 at the first place before displaying the cell.)
I've tried:
[tableView beginUpdates];
tableView endUpdates];
I've tried various animation options with:
[tableView reloadRowsAtIndexPaths:#[myPath] withRowAnimation:UITableViewRowAnimation(Bottom,Top,None etc)];
I've tried:
[tableView reloadSections:[NSIndexSet indexSetWithIndex:myPath.section] withRowAnimation:UITableViewRowAnimation(Top,Bottom,None etc)];
But they all yield the same result: the whole table view layout gets messed up: it jumps to another cell, some views go blank, cells overlap each other, video inside cells stop playing, and the label doesn't expand (but refreshes inside itself, e.g. that short preview txt with one line animates from top/bottom etc but doesn't expand).
What might be causing the mess up of the whole table view and how I can reload just one cell correctly with and expansion animation, without messing up the whole layout? I've seen many questions and answers regarding this, but they all recommend the options that I've already tried and explained above.
My app targets iOS 8.0+
UPDATE: Here is the relevant code (with some parts regarding inner workings that aren't related to layout, removed):
MyCell *cell = (MyCell *)[tableView dequeueReusableCellWithIdentifier: MyCellIdentifier forIndexPath:indexPath];
cell.delegate = self;
cell.indexPathToReloadOnAnimation = indexPath;
cell.shouldShortenCaption = YES;
id post = self.posts[indexPath.section] ;
[cell setPost:post];
return cell;
And inside setPost::
if(self.shouldShortenCaption){
captionLabel.numberOfLines = 2;
}else{
captionLabel.numberOfLines = 0;
}
NSString *text = [some calculated (deterministic) text from post object];
Button action is simple:
self.shouldShortenCaption = NO;
[one of the reload codes that I've written above in the question]
UPDATE 2: Here are some more methods regarding the issue:
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
if (section < self.posts.count) {
return 59;
}else
return 0;
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
if (section < self.posts.count) {
MyFeedHeader *header = [tableView dequeueReusableCellWithIdentifier:MyFeedHeaderIdentifier];
header.delegate = self;
[header setPostIndex:section];
[header setPost:self.posts[section]] ;
return header;
}
else
return nil;
}
Header's setPost: method basically sets the relevant texts to labels (which have nothing to do with the caption, they are completely different cells. the problematic cell is not the header cell). The table doesn't have any footer methods. The only method regarding height is the one above.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (section >= self.posts.count) {
return 1;
}
id post = self.posts[section];
[calculate number of rows, which is deterministic]
return [number of rows];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
BOOL hasCursor = self.cursor && self.hasMore ? 1 : 0;
return self.posts.count + hasCursor;
}
(Post count and cursor and hasMore are also deterministic).
UPDATE 3: I've asked a question (which wasn't a duplicate, even though there are similar questions) and got a useful answer that solved my problem. Can the downvoters please elaborate the reason that they've downvoted?
Here is an example: https://github.com/DonMag/DynamicCellHeight
Table B is one way of accomplishing "More/Less" (Table A was for another layout I played around with). It uses the [tableView beginUpdates]; tableView endUpdates]; method of triggering the table re-layout.
The key is getting all your constraints set up correctly, so the Auto-Layout engine does what you expect.
The example is in Swift, but should be really easily translated back to Obj-C (I think I did it in Obj-C first).
Edit: some additional notes...
This is using a pretty standard method of dynamic-height table view cells. The vertical spacing constraints between elements effectively "pushes out" the bounds of the cell. The tap here toggles the numberOfLines property of the label between 2 and 0 (zero meaning as many lines as necessary). Sequential calls to beginUpdates / endUpdates on the table view tells the auto-layout engine to recalculate the row heights without needing to reload the data.
For this example, I did use a little "trickery" to get the smooth expand/collapse effect... The multiline label you see here is contained in a UIView (with clipsToBounds true). There is a second, duplicate multiline label (alpha 0 so it's not visible) that is controlling the height. I found that changing the numberOfLines on the visible label sort of "snapped" to 2 lines, and then the size change animation took place... resulting in the text "jumping around."
Just for the heck of it, I added the "not so good" version to my GitHub repo for comparison's sake.
I have a UITableViewCell with a description label pinned to the bottom as shown below:
Tapping on the description label toggles the numberOfLines between 3 and 0:
- (void)awakeFromNib {
[super awakeFromNib];
[self setupView];
}
-(void) setupView
{
UITapGestureRecognizer * gestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(toggleNumberOfLines)];
self.jobDescriptionLabel.userInteractionEnabled = YES;
[self.jobDescriptionLabel addGestureRecognizer:gestureRecognizer];
}
- (void) toggleNumberOfLines {
if(self.jobDescriptionLabel.numberOfLines != 0){
self.jobDescriptionLabel.numberOfLines = 0;
}else{
self.jobDescriptionLabel.numberOfLines = kNumberOfLines;
}
[self.jobDescriptionLabel sizeToFit];
[self layoutIfNeeded];
}
When I tap on the label, the number of lines does change but the cell does not expand to accommodate the new number of lines. How do I fix this?
Collapsed (Default):
Expanded:
If you have your constraints set up correctly, you do not need to reload the data --- not the whole table, not even the affected rows.
Best method is to add a delegate function back to your tableview controller, and just call these lines back-to-back:
tableView.beginUpdates()
tablebleView.endUpdates()
That will tell auto-layout to re-calc the row heights.
Edit: Check my answer - which includes a link to a demo project - here: Expand UILabel inside UITableView with "more" button like Instagram
When the cell lays out it's subviews, the table view doesn't actually have any way of knowing anything has changed. You manually have to tell the table view to recalculate.
You probably want to reload the table view, or at least the cell the changes are happening in.
Take a look at reloadData for reloading the whole table view, or reloadRowsAtIndexPaths: if you want to just reload specific indexes in the Apple reference docs.
You can update the tableview cell height in heightForRowAtindexPath:.
Call reloadRowsAtIndexPath Method to update single row of UITableView.
self.dataTableView.beginUpdates()
self.dataTableView.reloadRows(at: [IndexPath(row: 0, section: 0)], with: UITableViewRowAnimation.automatic)
self.dataTableView.endUpdates()
I do not think is possible but is it a way to select a particular section in UITableView? I would like to make it possible for user to add a row to that section.
The problem that section normaly has a 22px height, so it is too small to select it, and there is no such a delegate method.
I believe you are trying to use section header, there is no such method, but you can add UIButton or UITapGestureRecognizer to section header view and that you can insert row to that section. Use method:
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
Can not understand your question correctly but you can use tableview default method to insert row at specific Section and Row using insertRowsAtIndexPaths method shown below.
Add UITapGestureRecognizer on your headerview which you have created using viewForHeaderInSection and set tag = section like yourView.tag = section; and in your tapGestureSelected method.
-(void)tapGestRec:(UIGestureRecognizer *)gest
{
// Here you will know which section is tapped using gest.view.tag
NSLog(#"You have selected : %d Section",gest.view.tag);
[yourTableView insertRowsAtIndexPaths:#[[NSIndexPath indexPathForItem:0 inSection:gest.view.tag]] withRowAnimation:UITableViewRowAnimationAutomatic];
}
I have a UITableView with two sections. Items can move from one section to another, as seen in the image below.
The basic interaction is as follows:
you swipe a cell/row
that cell is removed from its section
that cell is added to the bottom of the other section
Note, I'm using a sequence of deleteRowsAtIndexPaths and insertRowsAtIndexPaths with CATransactions. Remove followed by add isn't the only table operation in place; for the general approach I want chained insert/delete/update animations to start when the preceding operation has finished. Here's an example:
[CATransaction begin];
[CATransaction setCompletionBlock:^{
[table beginUpdates];
// update data source...
// insertRows...
[table endUpdates];
}];
[table beginUpdates];
// update data source...
// deleteRows...
[table endUpdates];
[CATransaction commit];
When there are enough items in the table view to take up more than a screen's worth of space, (what I think is) the default UITableView scrolling kicks in when cells are removed from one section and added to another.
My assumption (as I haven't found any reference to scroll behaviour in the docs) is that the UITableView scrolls to place/keep the inserted row in view.
Consider the example of swiping a cell to remove it from one section and have it added to the other:
the cell is currently in view because the user is swiping it (so perhaps there is no change/scroll required for the deleteRows...
(in some circumstances) the table view scrolls for the subsequent insertRows...; the scroll happens before the insert so that the animated insert is observed
For example, in the image below, swipe one of the cells in the bottom section, table view scrolls, then inserts at the bottom of the top section...
I say some circumstances because this behaviour is observed if the insert takes place in the top section (section 0). When the delete / insert is from section 0 to section 1, the table view does not scroll at all.
I'm also seeing some other behaviour I don't understand. In the case where some of the cells have more than one line of text (cells aren't all the same size) - the scroll seems to "not work". At first the scroll amount simply appeared wrong; however, testing with all the cells being single line revealed consistent scrolling when the insert occurred in section 0.
The following image shows the wrong scrolling:
I have two specific questions and one general question.
Specific question #1: is there a hierarchy for UITableViews such that section 0 is preferred, so the table view will scroll to section 0 so that inserts are observed, but not scroll to other sections?
Specific question #2: is it the height of the cells in the above example that is causing the wrong scrolling to be observed?
This is the code I'm using for calculating the height of the table view cells:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSInteger index = indexPath.row;
ParsedItem* item;
if (indexPath.section == 0) {
item = [_parsedItemList.toDoList objectAtIndex:index];
}
else {
item = [_parsedItemList.doneList objectAtIndex:index];
}
NSAttributedString* attrString = [[NSAttributedString alloc] initWithString:item.displayText attributes:_tableViewTextAttrNormal];
CGFloat height = [self textViewHeightForAttributedText:attrString andWidth:300.00];
return fmaxf(50.0, height);
}
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 50.0;
}
- (CGFloat)textViewHeightForAttributedText:(NSAttributedString*)text andWidth:(CGFloat)width {
UITextView* calculationView = [[UITextView alloc] init];
[calculationView setAttributedText:text];
CGSize size = [calculationView sizeThatFits:CGSizeMake(width, FLT_MAX)];
return size.height;
}
General question: as I mentioned above, I haven't found anything in the docs that describes how the scrolling is supposed to work. Is there a general rule or principal that I've missed? I don't want to add explicit scroll operations if I'm simply doing it wrong...
I'm using a UITableView with static cells for the settings view of my application.
One of the sections of this table contains two cells, the second one only being visible when a UISwitch contained in the first one is turned on.
So the UISwitch's target is this:
- (void)notificationSwitchChanged:(id)sender
{
UISwitch* switch = (UISwitch*)sender;
self.bNotify = switch.on;
[self.settingsTable reloadSections:[NSIndexSet indexSetWithIndex:1] withRowAnimation:UITableViewRowAnimationTop];
}
I implemented numberOfRowsInSection: this way:
(NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section
{
if (section != 1)
return [super tableView:tableView numberOfRowsInSection:section];
if (self.bNotify)
return 2;
return 1;
}
This "works", but the animation is faulty, making the first cell disappear or slide upwards halfway below the previous section or various stuff depending on the animation type I choose. The cleanest is UITableViewRowAnimationNone but is still not perfect.
If I scroll the section out of view and back it looks normal again.
EDIT: took a few screenshots of the problem:
I tried adding beginUpdates and endUpdates before and after reloadSections but it didn't change anything. Using reloadData instead of reloadSections works but there's no animation at all.
Is it because the table's cells are static or am I missing something?
I think this method should solve your problem.
[self.tableView insertSections:<#(NSIndexSet *)#> withRowAnimation:<#(UITableViewRowAnimation)#>];
Just update the data source (you dont have to because your are updating the section count depending on some flags) and call it.
Maybe you have to use this method instead.
[self.tableView insertRowsAtIndexPaths:<#(NSArray *)#> withRowAnimation:<#(UITableViewRowAnimation)#>];