Dynamically change the section header text on a static cell - ios

I have a UITableViewController, with its table view having static cells, defined in a storyboard.
My table view has two sections. The first with two cells, and the second section has three cells. The second section also has text in its header.
What I would like to do is that when the user taps the first or second cells in the first section, to update the header text of the second section. Do so dynamically and with dynamic content (say the date and time is displayed there as of the moment they tap cells).
I have tried numerous things, but the viewForHeaderSection is only called once.
I registered the header section cell with
tableView.registerClass(TableSectionHeader.self, forHeaderFooterViewReuseIdentifier: "secondSectionCell")
Where TableSectionHeader is simply:
class TableSectionHeader: UITableViewHeaderFooterView { }
I am then able to dynamically set the section header, like so:
override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
if section == 1 {
if let cell = tableView.dequeueReusableHeaderFooterViewWithIdentifier("secondSectionCell") {
cell.textLabel?.text = "hello world"
return cell
}
}
return nil
}
I also have implemented the following override, since some people suggest that when implementing viewForHeaderInSection, it is also required:
override func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 40.0
}
Even still. viewForHeaderInSection is only called once.
Am I able to somehow refresh the section header text dynamically as described above?

You can actually achieve this using traditional table view way easily.
Even though it is static UITableView, in your dataSource view controller, you can still implement - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
So how do you update the title on the fly? Create a property for this view controller, say NSString *titleForSecondSection. Whenever user tap the cells in the first section, you just need to update this property in the callback - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
The last step is to call [self.tableView reload] after you modified the titleForSecondSection property. Or if you don't want to reload the whole table view, just call - (void)reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation
To be clear, in your - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section, for sections that don't need to change title, just return a static string. For sections that need to change title, return the dynamic property you created.

viewForHeaderInSection would only be called when the tableview is reloaded. So assuming you don't want to reload the whole tableview, you might need to change the content of label directly.
pseudo codes like:
var label_first_section_header //as global variable
then in viewForHeaderInSection just point it to the label
if section == 1 {
if let cell = tableView.dequeueReusableHeaderFooterViewWithIdentifier("secondSectionCell") {
cell.textLabel?.text = "hello world"
label_first_section_header = cell.textLabel
return cell
}
}
then you can change the text dynamically whenever you want, for example in didSelectRowAtIndexPath

Related

UITableView space between row and sections

I want to make a UITableView as close to the iOS settings view as possible:
How do i create the space between different sections? What's the best way?
You just select grouped style in the Interface Builder and it will separate the table per section, similar as is in the screenshot.
Here is an attachment:
Change Style of UITableView to "Grouped"
try these..
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return 30; //according to your need..
}
i hope it helps..
First of all the iOS Settings doesn't use the ViewForSection otherwise the Section view would be placed in the top tableView.
You can return an empty containerView cell for that specific indexPath and stop didSelect in it, but remember to include the empty cells to the array you are using for other cells.
Just add the footer height in the table as required:
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 44
}

UITableView duplicate section header after updating row heights

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
}
}

Mixing storyboard prototype cells with auto populating data

I have an auto populating UITableView from a NSMutableArray. I need to add a static cell to the top of the table from a prototype I built in storyboard. What is the correct way of doing this?
When I just added the prototype to the storyboard editor, it started out by covering up the first cell. After reloading the data, it would eventually appear under the other cells.
To make "space" for it, I made the table "editable" with the following code -
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return self.editing ? wOutputArray.count + 1 : wOutputArray.count;
}
Now it doesn't cover any cells, but it seems as though it doesn't have a home. At initial view load it's at the top of the table, then after a few refreshes it eventually falls back to the bottom of the table.
EDIT:
I'm reading that you can't mix static cells with dynamic cells, and that the table should be split up into sections.
So here I changed-
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableViewB{
return 2;
}
&
- (NSInteger)tableView:(UITableView *)tableViewB numberOfRowsInSection:(NSInteger)section{
switch(section){
case 0:
return 1;
break;
case 1:
return wOutputArray.count;
break;
}
return 0;
}
But I'm not sure what to do next... Is this the right direction?
Technically, both types of cells that you want to use are prototype cells - they are just different types of cell.
If you just want a static cell at the top of the others then all you need to do is account for it in your table data source methods.
First let the tableview know that there will 1+(dynamic cells count) cells
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return wOutputArray.count + 1
}
Second, in cellForRowAtIndexPath return the appropriate cell, accounting for the fact that indexPath.row will be 1 more than the element of your array (because row 0 is the static cell)
func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
var cell
if (indexPath.row==0) {
cell=tableView.dequeueReuseableCellWithIdentifier("staticCell",forIndexPath:indexPath)
// Any other manipulation as required
}
else {
cell=tableView.dequeueReuseableCellWithIdentifier("dynamicCell",forIndexPath:indexPath)
cell.label.text=wOutputArray[indexPath.row-1]; // Or whatever property you want to use
}
return cell!
}

Force cellForRowAtIndexPath to be called when numberOfRowsInSection equals to zero

I want cellForRowAtIndexPath to be always called even if numberOfRowsInSection ==0 as i'm hiding and showing a section inside the table by setting numberOfRowsInSection to zero but when i do that the method stated above doesn't executed as numberOfRowsInSection becomes zero which makes a problem as the contents of the cell remains rendered although the section header disappears, any idea on how to hide the contents of the cell after setting the numberOfRowsInSection to zero:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (section==0||section==1)
return 1;
if (section==2) {
if (self.isSectionAppears)
return 1;
else
return 0;
}
}
given that self.isSectionAppears is a bool property setted to false when i want to hide the section
You shouldn't be burying your section showing/hiding logic in your cellForRowAtIndexPath. When you need to tell the tableview to update the section header, you can do a refresh on the section with the code
NSUInteger sectionToRefresh = //index of table section with header you want to refresh.
NSIndexSet indexSetWithSection = [NSIndexSet indexSetWithIndex:sectionToRefresh];
[tableView reloadSections:indexSetWithSection withRowAnimation:UITableViewRowAnimationAutomatic];
If you'd like to refresh visual elements of your section header without refreshing the rest of the section, you could do so by acting on your section header object directly:
//Get section header from table
UITableViewHeaderFooterView* myHeader = [tableView headerViewForSection:sectionToRefresh];
//Update header
[myHeader.textLabel setText:newSectionHeaderText]; //Update it's text for example.

Detect when UITableViewCell goes off the screen

I'm implementing a rich UITableView with customly created UITableViewCell, I show these on the screen in one fashion, but once they go off the screen I want to take a note of that, since the second time they come on I would like them to get displayed in a different manner. Think auto "mark as read" when going off the screen.
I've been looking for some way to detect when a cell goes off the screen (get's deallocated or dequeued or equivalent), preferably in the UITableViewController class to make a quick note of the indexPath.row value, but in the UITableViewCell is equally as good.
I haven't been able to do this in any standard way. Counting the times it appeared seems out of the question as I do multiple reloadData calls on the table.
Anyone any ideas? This seems a bit tricky :)
This is an old question, but in case anyone is looking, in iOS6, a new UITableViewDelegate function was introduced that does just this:
- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
It does a great job at telling you whenever a cell is removed, however, it is very thorough and thus if you did a reload cell, even the old cell that's being replaced will trigger this delegate function. In my implementation I simply check to see if the indexPath passed is still within the array tableView.indexPathsForVisibleRows. Something like:
- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([tableView.indexPathsForVisibleRows indexOfObject:indexPath] == NSNotFound)
{
// This indeed is an indexPath no longer visible
// Do something to this non-visible cell...
}
}
I think you could use the
- (NSArray *)visibleCells
method for your UITableView. This returns an array of all cells that are visible. You can then mark any data that is "not visible" (i.e. not in this array) in the way you want, such that when you scroll back to it, it has been updated.
Hope that helps
Once UITableViewCell is invisible, it will be removed from UITableView. You may override the method -(void)removeFromSuperView, and do something within the method. At last, do not forget to call [super removeFromSuperView].
The prepareForReuse method on UITableViewCell that Andrey Tarantsov mentions looks good. Putting a couple of NSLogs in there allows you to print out the values of any variables of the cell. Any thoughts as to how this could be set back to the table view controller?
Are you sure a cell going offscreen is exactly what you want to catch? If you want to mark items as read, this does not seem like a proper way to do it. For example, I might scroll though the table really fast, and I would be very surprised if you marked all of the stuff as read.
As for the technical part, simply keep a list of cells that are on screen (cellForRowAtIndexPath should add cells to that list), and in scrollViewDidScroll delegate method check if any of them are no longer visible.
Another possible idea: I remember there is prepareForReuse method on the cell. Not sure when it is called, though.
I know this is a REALLY old question, but in case anyone is looking for an answer for Swift 5:
func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
<#code#>
}
I think I would try periodically checking the indexPathsForVisibleRows property of the UITableView. From the largest index path, you can deduce that all previous rows have been scrolled past.
I needed to get some data from the cell as it was scrolled off of the screen. I used #Mr.T's answer however it doesn't state how to get the data.
Say for example the name of the cell class that I'm using is MyCell and it has a data model in it named MyModel with a property of postId. I initially set that info in cellForItem:
var datasource = [MyModel]()
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! MyCell
cell.myModel = datasource[indexPath.item] // an individual instance of MyModel from the array
print("cellForItem - indexPath.item: ", indexPath.item) // if the was the very first cell coming on it would print 0
print("postId: ", cell.myModel.postId) // maybe the postId is qwerty
return
}
To get some data from the cell as it is scrolled off of the screen:
func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
guard let myCell = cell as? MyCell else { return } // You must cast the cell from the method param to your cell type which for me is MyCell
print("didEndDisplayingCell - indexPath.item: ", indexPath.item) // if this was the very first cell scrolling off it should print 0
print("postId: ", myCell.myModel.postId) // the postId should be qwerty
}
The best way to test this is to add a small amount of cells to your collectionView, like first 2 cells, then later on 3 cells, then later on 4 cells. Then just scroll off the very first cell and see what is printed out. Do it for each cell. The indexPath.item and postId should both match for cellForItem and didEndDisplaying.

Resources