I'm trying to change the header title for a section in a UITableView when a cell from that section is selected. tableView:titleForHeaderInSection is triggered by the application, so that doesn't help. I can call reloadData, but the performance suffers because the app has to reload all the visible cells. I also tried to use a custom header, but that also results in some performance problems.
Is there any way to get a handle to the UILabel that the default header view uses and change its text manually?
Calling [tableView endUpdates] may provide the desired results without too much of a performance hit.
[self.tableView beginUpdates];
[self.tableView endUpdates];
// forces the tableView to ask its delegate/datasource the following:
// numberOfSectionsInTableView:
// tableView:titleForHeaderInSection:
// tableView:titleForFooterInSection:
// tableView:viewForHeaderInSection:
// tableView:viewForFooterInSection:
// tableView:heightForHeaderInSection:
// tableView:heightForFooterInSection:
// tableView:numberOfRowsInSection:
with:
[self.tableView headerViewForSection:i]
you could get the view for Section i and then "update" it manually
this even works if your view is just the auto-generated label, but you will have to resize it yourself.
so if you try to:
[self.tableView headerViewForSection:i].textLabel.text = [self tableView:self.tableView titleForHeaderInSection:i];
you will set the text, but you would not set the label-size. You can get the needed size from NSString to set it yourself:
[label.text sizeWithFont:label.font];
There doesn't appear to be any standard API for accessing the system-provided section header view. Have you tried the more targeted reloadSections:withRowAnimation to get UIKit to display the new header text?
What kind of performance issues were you seeing with custom section header views? I doubt that the standard one is much more than just a UILabel.
You can set the title for the section header label directly. For example, to set the title of section zero:
UITableViewHeaderFooterView *sectionZeroHeader = [self.tableView headerViewForSection:0];
NSString *sectionZeroLabel = #"Section Zero";
[sectionZeroHeader.textLabel setText:[sectionZeroLabel uppercaseString]];
[sectionZeroHeader setNeedsLayout];
Be sure and tell the section header view it needs layout, otherwise the new text may be truncated. Also, section labels are usually all uppercase.
Since a UITableView does not enqueue and dequeue section header views for reuse, you might as well see if it's feasible to store all the section header views in memory. Note you'll have to create your own section header views with the background and etc., to do this, but it allows you a bit more flexibility and capability.
You could also try tagging the section header views (also requires you to create your own section header views) and just grab them from the tableview as needed.
This is a WAG, and I can think of lots of reasons why it might not work, but couldn't you iterate through the subviews, and find the one you are looking for? E.g.
for (UIView *v in self.tableView.subviews) {
// ... is this the one?
}
One of the solution would be to manage external array for multiple section headers containing label references and update them externally.
For completeness, the method we are all looking for is this private API named exactly what you'd expect:
-(void)_reloadSectionHeaderFooters:withRowAnimation:
eg:
[tableView _reloadSectionHeaderFooters:[NSIndexSet indexSetWithIndex:indexPath.section] withRowAnimation:UITableViewRowAnimationAutomatic]
I do not recommend using this, however.
If you want to update only one section best way is tableView.headerView. If header is not visible it returns nil, so it doesn't load extra headers.
if let header = tableView.headerView(forSection: i) {
header.textLabel!.text = "new title"
header.setNeedLayout()
}
If you want to update all visible section headers it is better to set header tag as section before view is displayed and enumerate subviews when needed:
func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
view.tag = section
// additional customization
}
func udpateVisibleSectionHeaders() {
for subview in tableView.subviews {
if let header = subview as? UITableViewHeaderFooterView {
let section = header.tag
header.textLabel!.text = "new title"
header.setNeedsLayout()
}
}
}
Don't forget to call setNeedsLayout or label can be truncated.
Related
In my cell.xib, I have a label, with constraints to all its sides. I've set that label to lines = 0 and line-break = word wrap. Then, I do this to my TableView:
self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.estimatedRowHeight = 100.0
Everything works great, and my UITableViewCell is auto-height. If the text is long, then my tableView intelligently calculates the size.
The problem is -- how do I tell my UITableView to "re-calculate" the size once the content changes in the cell?
My cell could call its delegate, and in this delegate, I'd like the TableView to re-draw the height.
Right now, the content in my cells change constantly, but the cell height never changes.
There is a documented way to do this. See UITableView.beginUpdates() documentation:
You can also use this method followed by the endUpdates method to animate the change in the row heights without reloading the cell.
So, the correct solution is:
tableView.beginUpdates()
tableView.endUpdates()
Also note that there is a feature that is not documented - you can add a completion handler for the update animation here, too:
tableView.beginUpdates()
CATransaction.setCompletionBlock {
// this will be called when the update animation ends
}
tableView.endUpdates()
However, tread lightly, it's not documented (but it works because UITableView uses a CATransaction for the animation).
I've found the best way to get it to check heights is to call, after whatever text change has been made, in order:
self.tableView.beginUpdates()
self.tableView.endUpdates()
This causes the tableView to check heights for all visible cells and it will cause changes to be made as needed.
I think the simplest solution is to reload that specific cell. For example:
- (void)yourDelegateMethodOfCell:(UITableViewCell *)cell {
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
//If cell is not visible then indexPath will be nil so,
if (indexPath) {
[self.tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
}
You can get automatic cell height by this code
tableView.beginUpdates()
// add label text update code here
// label.numberOfLines = label.numberOfLines == 0 ? 1 : 0
tableView.endUpdates()
Below is the reference to this solution with demo :
GitHub-RayFix-MultiLineDemo
I got your point, and I met the problem before.
Your cell is in AutoLayout, and you wish the cell changes by itself. Here is recommended answer for that: Using Auto Layout in UITableView for dynamic cell layouts & variable row heights , so we don't talk about that again.
So here we focus on your problem.
Since the content of your cell changes constantly, which means the content has updated. Here we suppose the content is a label. We set the label's text, and surely the label's height maybe change.
Here comes the point: How does the label's change inform the cell to update?
We use AutoLayout, surely we have to update the constraint of height for the label.
And I think it will work!
Below is the detail step:
1. We setup the constraints for the cell's subviews.(I think it's done)
2. One of the label's height is changed by itself.(I think it's done too)
3. We get the new height of the label, and update the constraint of height for the label.(what we have to do)
Seems you wanted to reload the particular cell/cells based on content changes
Here we have a couple of options
1) Need to reload the entire table view .
or else
2) Reload particular cell/cells based on content changes.
But the preferred option would be reloading the particular cell,
Why because
when you asked your UITableView instance to reload a couple of cells,tableview will asks its datasource(-tableView:cellForRowAtIndexPath:) to get the updated content,so that the reloaded cells will have the updated size & Updated content aswell.
Try to reload the cells when the content/height need to update based on content
Hope that helps!
Happy coding :)
Take a look at this answer : Using Auto Layout in UITableView for dynamic cell layouts & variable row heights
How to achieve dynamic cell size is described very thorough there.
As a suggestion for testing try adding setNeedsLayout to:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
or
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
To disable the annoying tableView animation:
UIView.setAnimationsEnabled(false)
tableView.beginUpdates()
// cell.titleLabel?.text = "title"
// cell.detailTextLabel?.text = "Very long text ..."
// cell.detailTextLabel?.numberOfLines = 0
tableView.endUpdates()
UIView.setAnimationsEnabled(true)
You can resize your cell height by implementing below method only
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath{
return UITableViewAutomaticDimension;
}
In my iOS app, I have a UITextView inside a tableview cell.
The UITextView and hence the cell height expands when the frame required for the text entered by user exceeds the current height of the cell.
In order to achieve the above, I am calling [tableView beginUpdates] followed by [tableView endUpdates] to reload the height for the cells.
The above is resulting duplicate section headers overlapping the expanded cell.
Is there a way to fix this without calling [tableView reloadData]?
Appended below is some relevant code:
When there is a text change, I verify if the text will fit in current text view, if not the cell is expanded to the new height:
- (void)textViewDidChange:(UITextView *)textView {
CGFloat oldTextViewHeight = [(NSNumber *)[self.cachedTextViewHeightsDictionary objectForKey:indexPath] floatValue];
CGFloat newTextViewHeight = [textView sizeThatFits:CGSizeMake(textView.frame.size.width, CGFLOAT_MAX)].height + CELL_HEIGHT_PADDING;
if (newTextViewHeight > oldTextViewHeight ||
(newTextViewHeight != oldTextViewHeight && oldTextViewHeight != TEXTVIEW_CELL_TEXTVIEW_HEIGHT)) {
[self reloadRowHeights];
}
}
- (void)reloadRowHeights {
// This will cause an animated update of the height of the UITableViewCell
[self.tableView beginUpdates];
[self.tableView endUpdates];
}
It's also important to note that I am using a custom section header, which makes my problem similar to one mentioned here:
UITableView Custom Section Header, duplicate issue
I cannot however use the solution to above problem because I cannot reloadData for the tableView in middle of user entering text.
Try implementing
tableView(_ tableView: UITableView, estimatedHeightForHeaderInSection section: Int)
delegate method if you didn't
Little late to the party, but I couldn't find a working solution on SO, and then I figured one out, so I thought I'd share.
I use UITableViewAutomaticDimension both for cell heights and for section header heights. My header view class is just a UIView subclass with some subviews as needed. Inside my tableView(:viewForHeaderInSection:) class, I just initialized a new header view as needed, and I was experiencing this duplicate headers issue. Not even reloadData helped.
What seems to have fixed it for me was to implement basic "cell re-use" for the headers. Something like this:
Store the header views in a dictionary somewhere in your view controller.
class ViewController: UIViewController {
var sectionHeaders: [Int: UIView] = [:]
// etc...
}
Then, upon request, return your existing section header view if available, or else create and store a new one.
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
if let sectionHeader = self.sectionHeaders[section] {
return sectionHeader
} else {
let sectionHeader = YourSectionHeader()
// Setup as needed...
self.sectionHeaders[section] = sectionHeader
return sectionHeader
}
}
Current I am creating a prototype cell in storyboard and using this cell as a section header.
Inside tableView:viewForHeaderInSection: method, I am dequeuing the cell and returning it.
My section header cell has a UITextField and a UIButton in it.
When I tap on text field keyboard appears but as soon as focus is moved away from text field whole section header disappears.
This happens when I return the cell directly as section header view, but if I return a newly allocated UIView as section header view onto which cell is added as subview then everything works fine besides autoresizing masks.
Why header is disappearing?
I am not sure what could be the best thing todo here.
-(UIView *) tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
static NSString *CellIdentifier = #"SectionHeader";
SettingsTableViewCell *sectionHeaderCell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
//return sectionHeaderCell; // returning cell directly, section header disappears when focus is moved away from text field.
UIView * headerView = [[UIView alloc] initWithFrame:sectionHeaderCell.frame];
[headerView addSubView:sectionHeaderCell];
return sectionHeaderCell;//header view never disappears, but auto resizing masks do not work. Need to know how to set autoresizing masks to headerView so that it resizes correctly.
}
Prototype cell table views only allow you to design cells in the storyboard editor, not section headers and footers. Your attempt to use a UITableViewCell as the section header is a clever hack, but it's just not supported by the classes involved—UITableViewCell is not designed to be used for anything other than a table view cell. It could do a lot worse than the view disappearing or not being laid out correctly; UIKit would be well within its rights to fail an assertion, delete all the app's data, revoke your developer certificate, or set your house on fire.
If you want your code to function properly, your choices are to either create your section headers in code or to put them in a separate XIB file. I know that's not what you want to do, but those are the options you have.
I had the same issue and the fix was to return the cell's contentView like:
-(UIView *) tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
static NSString *CellIdentifier = #"SectionHeader";
SettingsTableViewCell *sectionHeaderCell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
sectionHeaderCell.myPrettyLabel.text = #"Greetings";
sectionHeaderCell.contentView.backgroundColor = [UIColor whiteColor]; // don't leave this transparent
return sectionHeaderCell.contentView;
}
And you get the same autolayouted results as before, but without the disappearing.
I am sure you can use UITableViewCell as a section header, because UITableViewCell is subclass of UIView, so according to LSP
“objects in a program should be replaceable with instances of their
subtypes without altering the correctness of that program.”
In iOS 8, it's simple really. Just design your header the same way you design your cell. Everything is the same, you can put custom class and don't forget to add reuse identifier.
When it comes to use it in the code, just return that cell in tableView:viewForHeaderInSection method.
Don't forget to implement tableView:heightForHeaderInSection if you want to use fix height or tableView:estimatedHeightForHeaderInSection if the height depends on the cell intrinsic size.
I'm trying to create a view similar to add/edit contact in iOS and there are a few things that are happening, and I'm not sure how they are implemented. Any help in understanding is greatly appreciated.
For each section in contacts i.e. name, phone number, email, etc are these each their own tableview or are these sections within a larger tableview?
When clicking done when adding or editing a contact, the unused tableview cells disappear. Is this using deleteRowsAtIndexPaths:withRowAnimation: or is there a hide method I haven't found? If it is using that method, then when clicking the edit contact button, how does the view brings back these unused tableview cells?
When clicking on a cell in the tableview cell when editing a contact, you are able to change the text. Is this a textfield within a tableview cell or is it actually modifying the label of the tableview cell?
I am not looking for any specific code, as a fairly new programmer I am just trying to understand the strategies/best way to implement these features.
I tried a lot different ways to implement that. the easiest one: Subclass UITableViewCell and overwrite setFrame:. note that this is easy to achieve for grouped tables, but hard for plain ones. in the datasource's tableView:cellForRowAtIndexPath: create an object of this custom cell for the first section. use another identifier for cells of that section, so that only the correct cells will be re-used.
yes, I assume that. The controller has some sort of definition how many cells has to be show in edit mode and how many are actually used with some sort of information. you can easily create a array of indexPaths that must be deleted.
I would do it in tableView:didSelectRowAtIndexPath: by fetching the cell via tableView:cellForRowAtIndexPath:, hide the label and unhide or add a textfield and make this first responder.
code for 1.
the cell
#interface InsetCell : UITableViewCell
#property(nonatomic)CGFloat inset;
#end
#implementation InsetCell
- (void)setFrame:(CGRect)frame {
CGFloat inset;
if (_inset == 0) {
inset = 70; //default value
} else {
inset = _inset;
}
frame.origin.x += inset;
[super setFrame:frame];
}
-(void)setInset:(CGFloat)inset
{
_inset = inset;
[self setNeedsLayout];
}
#end
a project that uses similar code
I have a UITableView with some sections, each has its own header view.
When user taps on the header view of a section, all rows of that section will collapse. What i do is, I set the number of row of that section to 0, and then call :
[self.tableView reloadSections:sections withRowAnimation:UITableViewRowAnimationBottom];
Everything works as expected, except one thing : the header view of the section becomes white blank. When i scroll the table, then the header becomes normal again.
So i guess there's some problem with the drawing of the table.
One funny thing is, if i use UITableViewRowAnimationFade instead, then even when i scroll the table, the header is still white blank.
When I update just ONE section there is also no problem - when I update more than one section the problem occurs.
If i use
[self.tableView reloadData]
instead, then everything works fine.
The reason i use
[self.tableView reloadSections:sections withRowAnimation:UITableViewRowAnimationBottom];
is because i want animation.
Wrapping with beginUpdates / endupdates does not work.
I realize that it's a LONG time since this question was posed but I think all of the answers given below are incorrect.
I had the same problem (with someone else's code) and was about to be fooled by this post when I realized that the code was not doing it's reuse of the table header correctly. The header disappearing was because the code was only supplying a UIView, not a UITableViewHeaderFooterView, registered correctly and set up for reuse.
Have a look at the answer here:
How to use UITableViewHeaderFooterView?
My blank headers went away when I set up a reusable header.
In Swift:
You need to create a class that's a subclass of UITableViewHeaderFooterView and register it to the table view. Then in viewForHeaderInSection, you do let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: "HeaderView") as! YourHeaderView, similar to what you do for UITableViewCells. Then return that header.
The deceptive thing is the function calls for a return of UIView? when it really needs a dequeuedReusableHeaderFooterView or reloadData will cause it to disappear
I found a work-around - not very elegant, but it works.
Instead of providing a NSIndexSet with more than one section, I call the reloadSections within a for-loop with only one section in each call.
looks like:
for (Element *eleSection in self.gruppenKoepfe) {
if ( eleSection.type.integerValue == qmObjectTypeFormularSpalte || eleSection.type.integerValue == qmObjectTypeFormularZeile ) {
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:nCount] withRowAnimation:UITableViewRowAnimationFade];
}
nCount ++;
}
I was having a similar problem, except I was trying to delete/insert sections, and I found that keeping a strong property pointing to the entire header view (i.e. not just a subview) stopped it disappearing during section updates.
e.g. Property and instantiation
#property (nonatomic, strong) UIView *headerView;
-(UIView *)headerView {
if ( !_headerView ) {
_headerView = [[UIView alloc] initWithFrame:CGRectMake(0,0,self.view.bounds.size.width, 300)];
// add additional view setup, including subviews
}
return _headerView;
}
And in tableView:(UITableView *)viewForHeaderInSection:(NSInteger)section:
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
if ( section == theCorrectSection ) {
return self.headerView;
}
return nil;
}
I had the same issue and it was none of the above. My problem was that i was hidding the first header and for some reason after more then 10 reloads the header at index 2 was set hidden. When i set headerView.hidden = false it was ok.