UITableView with fixed section headers - ios

Greets,
I'm reading that the default behaviour of UITableView is to pin section header rows to the top of the table as you scroll through the sections until the next section pushes the previos section row out of view.
I have a UITableView inside a UIViewController and this does not seem to be the case.
Is that just the defualt behaviour for UITableViewController?
Here's some simplified code based on what I have.
I'll show the UIController interface and each table view method I've implemented to create the table view.
I have a helper data source class that helps me index my objects for use with the table.
#interface MyUIViewController ()<UITableViewDelegate, UITableViewDataSource>
#property (nonatomic, readonly) UITableView *myTableView;
#property (nonatomic, readonly) MyCustomHelperDataSource *helperDataSource;
#end
//when section data is set, get details for each section and reload table on success
- (void)setSectionData:(NSArray *)sections {
super.sectionData = sections; //this array drives the sections
//get additional data for section details
[[RestKitService sharedClient] getSectionDetailsForSection:someId
success:^(RKObjectRequestOperation *operation, RKMappingResult *details) {
NSLog(#"Got section details data");
_helperDataSource = [[MyCustomHelperDataSource alloc] initWithSections:sections andDetails:details.array];
[myTableView reloadData];
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"Failed getting section details");
}];
}
#pragma mark <UITableViewDataSource, UITableViewDelegate>
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
if (!_helperDataSource) return 0;
return [_helperDataSource countSectionsWithDetails]; //number of section that have details rows, ignore any empty sections
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
//get the section object for the current section int
SectionObject *section = [_helperDataSource sectionObjectForSection:section];
//return the number of details rows for the section object at this section
return [_helperDataSource countOfSectionDetails:section.sectionId];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell * cell;
NSString *CellIdentifier = #"SectionDetailCell";
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier];
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
cell.textLabel.font = [UIFont systemFontOfSize:12.0f];
}
//get the detail object for this section
SectionObject *section = [_helperDataSource sectionObjectForSection:indexPath.section];
NSArray* detailsForSection = [_helperDataSource detailsForSection:section.sectionId] ;
SectionDetail *sd = (SectionDetail*)[detailsForSection objectAtIndex:indexPath.row];
cell.textLabel.text = sd.displayText;
cell.detailTextLabel.text = sd.subText;
cell.detailTextLabel.textColor = [UIColor blueTextColor];
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 50.0f;
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
return 30.0f;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger) section {
//get the section object for the current section
SectionObject *section = [_helperDataSource sectionObjectForSection:section];
NSString *title = #"%# (%d)";
return [NSString stringWithFormat:title, section.name, [_helperDataSource countOfSectionDetails:section.sectionId]];
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
UIView *header = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 260, 0)];
header.autoresizingMask = UIViewAutoresizingFlexibleWidth;
header.backgroundColor = [UIColor darkBackgroundColor];
SSLabel *label = [[SSLabel alloc] initWithFrame:CGRectMake(3, 3, 260, 24)];
label.font = [UIFont boldSystemFontOfSize:10.0f];
label.verticalTextAlignment = SSLabelVerticalTextAlignmentMiddle;
label.backgroundColor = [UIColor clearColor];
label.text = [self tableView:tableView titleForHeaderInSection:section];
label.textColor = [UIColor whiteColor];
label.shadowColor = [UIColor darkGrayColor];
label.shadowOffset = CGSizeMake(1.0, 1.0);
[header addSubview:label];
return header;
}

The headers only remain fixed (floating) when the UITableViewStyle property of the table is set to UITableViewStylePlain.
If you have it set to UITableViewStyleGrouped, the headers will scroll up with the cells (will not float).

Change your TableView Style:
self.tableview = [[UITableView alloc] initwithFrame:frame
style:UITableViewStyleGrouped];
As per apple documentation for UITableView:
UITableViewStylePlain- A plain table view. Any section headers or
footers are displayed as inline separators and float when the table
view is scrolled.
UITableViewStyleGrouped- A table view whose sections present distinct
groups of rows. The section headers and footers do not float.
Hope this small change will help you ..

Swift 3.0
Create a ViewController with the UITableViewDelegate and UITableViewDataSource protocols. Then create a tableView inside it, declaring its style to be UITableViewStyle.grouped. This will fix the headers.
lazy var tableView: UITableView = {
let view = UITableView(frame: UIScreen.main.bounds, style: UITableViewStyle.grouped)
view.delegate = self
view.dataSource = self
view.separatorStyle = .none
return view
}()

You can also set the tableview's bounces property to NO. This will keep the section headers non-floating/static, but then you also lose the bounce property of the tableview.

to make UITableView sections header not sticky or sticky:
change the table view's style - make it grouped for not sticky & make it plain for sticky section headers - do not forget: you can do it from storyboard without writing code. (click on your table view and change it is style from the right Side/ component menu)
if you have extra components such as custom views or etc. please check the table view's margins to create appropriate design. (such as height of header for sections & height of cell at index path, sections)

Related

UITableView - gap between main header and section header

Working on a UITableView, with a header in both the main tableview and each section. However, we are getting a weird gap between these two headers. We have tried the solutions given in other SO answers, such as:
self.edgesForExtendedLayout
tableView.contentInset
self.automaticallyAdjustsScrollViewInsets = false
But no luck!
I've put together a screenshot of what's happening:
The issue is the gap between the red and green headers, any help much appreciated, happy to share further snippets of the code if needed. The highlighted areas are matched in the view debugger, so I have confirmed it isn't an issue of extra padding anywhere.
Some relevant code:
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let sectionHeader = ShareScreenSectionHeaderView()
let title = viewData.sections[section].sectionTitle
let subtitle = viewData.sections[section].sectionSubtitle
sectionHeader.update(title: title, subtitle: subtitle)
return sectionHeader
}
The header set for the table view is a UIView, set using the tableHeaderView property
Here is the code snippet
#property (weak, nonatomic) IBOutlet UITableView *tblLoad;
#property (strong, nonatomic) IBOutlet UIView *lbltblHeader;
- (void)viewDidLoad {
[super viewDidLoad];
self.tblLoad.tableHeaderView = self.lbltblHeader;
self.tblLoad.delegate = self;
self.tblLoad.dataSource= self;
[self.tblLoad reloadData];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
return 2;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
if (section ==0) {
return 5;
}
return 1;
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{
return 50;
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section{
UIView *v =[[UIView alloc] initWithFrame:CGRectMake(0, 0, tableView.frame.size.width, 20)];
v.backgroundColor = UIColor.greenColor;
UILabel *lbl = [[UILabel alloc]initWithFrame:CGRectMake(10, 10, 200, 30) ];
lbl.text = [NSString stringWithFormat:#"I am the %ld Section ",section];
lbl.textColor = UIColor.blackColor;
[v addSubview:lbl];
return v;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
return 100;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
NSString *cellIdentifier = #"CellReuse";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier] ;
//you can customize your cell here because it will be used just for one row.
}
cell.textLabel.text = [NSString stringWithFormat:#"I am the %ld Cell ",(long)indexPath.row];
cell.textLabel.textColor = UIColor.whiteColor;
cell.backgroundColor = UIColor.lightGrayColor;
return cell;
}
I have created a new test project to testcase ur scenario, but i was not able to replicate as you can see there is no gap between the headerview and section header.

cellForRowAtIndexPath equivalent function for section

I would like to dynamically change an image in my tableview custom section header cell once the image finished being downloaded. However I do not how to get the reference to the section header cell. Is there an equivalent function for getting the header cell like there is for getting the table view cell using the function cellForRowAtIndexPath?
I tried but this is for the row. I need an equivalent for section header
if let cellToUpdate = tableView.cellForRowAtIndexPath(indexPath) as? ListingTableViewCell {
cellToUpdate.imageView.image = image!
}
Just set tags for section header view.
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section{
UIView *view = [UIView new];
view.backgroundColor = [UIColor orangeColor];
view.tag = section;
return view;
}
Then use tag to get the section header view.
NSInteger section2 = 2;
UIView *sectionHeader = [self.tableView viewWithTag:section2];
To customize your headers, you can override the following method from the UITableViewDataSource.
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
It works pretty much cellForRowAtIndexPath. You juste have to create your view and return it. Here is an example to set a UIImage as header of section 0 :
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
switch (section) {
case 0:
imageView.image = [UIImage imageNamed:#"image"];
return imageView;
break;
default:
break;
}
return [[UIView alloc] init];
}

Expand and collapse method for tableview in iOS

I created one tableview with 2 section and i displayed the array of the data in the tableview .. now i want to expand and collapse the section…
i am just a beginner please give any sample code for expand and collapse the tableview section…
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 2;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [arraytable count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellitem = #"simpletableitem";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellitem];
if (!cell) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellitem];
}
cell.textLabel.text = [arraytable objectAtIndex:[indexPath row]];
return cell;
}
This is my cellForRowAtIndexPath and arraytable is my array i give it in view did load
arraytable = #[#"hari12",#"narayanan",#"Praba",#"Deepak",#"Sailesh",#"Ram charan"];
Here's a sample code I've made to achieve what you want.
You can use this as a general guideline, since there are plenty of ways to achieve this, other than what I've done.
I've noticed from your example above that you have a table view with 2 sections, so this is what I'll use for the sample code.
There are maybe better ways to implement the below, but that was at the top of my head, and I think it's pretty simple and straightforward.
I've also included explanations as comments in the code below.
Note that you'll probably need to change some variables' names to fit your code (such as self.tableView, if you use something else) and section1DataArray, section2DataArray
// I've declared 2 BOOL properties to hold whether each section is visible or not
#interface ViewController ()
#property (nonatomic) BOOL section1visible, section2visible;
#end
-(void)viewDidLoad {
// After creating the table view, I've set the footer view frame to CGRectZero,
// so when a table view is collapsed you won't have the table columns 'bounds'
self.tableView.tableFooterView = [[UIView alloc] initWithFrame: CGRectZero];
}
// Then I've created a custom header for each table since I've wanted to make the header
// name a button which collapse/expand the table view.
-(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
// Here I've set the height arbitrarily to be 50 points
return 50;
}
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
// Change the below frame to fit your needs. In my example, I've used a table view
// to be at the width of the screen, and the height of 50 points (as we've set above)
UIView *header = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 50)];
// Then create a button
// I've arbitrarily chosen a size of 100x20 and created a frame to be placed in the
// middle of the above header
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(header.frame.size.width/2 - 50, header.frame.size.height/2 - 10, 100, 20)];
// Now I'm setting the tag (for later use) and title of each button
if(section == 0) { // First section
[button setTitle:#"Section 1" forState:UIControlStateNormal];
button.tag = 0;
} else { // Else, second section, since we only have two
[button setTitle:#"Section 2" forState:UIControlStateNormal];
button.tag = 1;
}
// I've arbitrarily chose to set the button colour to gray colour
button.titleLabel.textColor = [UIColor grayColor];
// Then we need to actually add an action to the button
[button addTarget:self action:#selector(updateTableView:) forControlEvents:UIControlEventTouchUpInside];
// Then we need to add the button to the header view itself
[header addSubview:button];
return header;
}
// Then we need to update our tableView:numberOfRowsInSection: to check for our BOOL value
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if(section == 0 && self.section1visible) {
return [self.section1DataArray count];
} else if (section == 1 && self.section2visible) {
return [self.section2DataArray count];
} else {
return 0;
}
}
// Then we need to create the actual method the button calls
-(void)updateTableView:(UIButton *)sender {
// Check the button tag, to identify which header button was pressed
NSInteger section = sender.tag;
if(section == 0) {
self.section1visible = !self.section1visible;
} else { // section == 1
self.section2visible = !self.section2visible;
}
// Use an animation to update the UI to create a 'smooth' expand/collapse
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:section] withRowAnimation:UITableViewRowAnimationFade];
}
Good luck mate.

Custom table view cells show up blank

I have a regular view controller (not a table view controller) and on that view I have a tableview. My table view cells are custom and I just made them through storyboards (didn't do anything in the code) but when I run my application, the tableview is blank. Any ideas as to why tho is happening to me? I have looked at other things on here but all of these other scenarios have to do with the person using an NSArray to fill out the tableview in the code, but mine is custom so I am not doing that. Thanks for any help. And before you mark this duplicate, please actually read this.
my code is as follows:
#interface TTViewController ()
{
NSArray *messageComponents;
}
#end
#implementation TTViewController
#synthesize dateTimePicker, messageSetupTableView;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.title = #"Message Setup";
messageComponents = [[NSArray alloc] initWithObjects:#"Recipient",#"Message", #"Date",#"haha", nil];
messageSetupTableView.backgroundColor = [UIColor groupTableViewBackgroundColor];
messageSetupTableView.alpha = 0.9;
}
#pragma mark -
#pragma mark Table view data source
-(NSInteger) numberOfSectionsInTableView:(UITableView *) tableView{
return [messageComponents count];
}
-(NSInteger)tableView:(UITableView *) tableVew numberOfRowsInSection:(NSInteger)section{
return 1;
}
// Customize the appearance of table view cells.
- (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];
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
}
}
// Configure the cell.
cell.textLabel.text = [messageComponents objectAtIndex:indexPath.row];
cell.imageView.image = [UIImage imageNamed:[messageComponents objectAtIndex:indexPath.row]];
cell.backgroundColor = [UIColor redColor];
cell.textLabel.textColor = [UIColor whiteColor];
UIColor *selectedColor = [UIColor blueColor];
UIView *myBackgroundColor = [[UIView alloc] init];
[myBackgroundColor setBackgroundColor:selectedColor];
[cell setSelectedBackgroundView:myBackgroundColor];
return cell;
I want my table view to have a date picker in one section, a textview in the other, and a button and a few text fields in the other. Thanks
A couple of suggestions:
First make sure that UITableView's delegate and datasource are mapped or not either from IB or programmatically.
Put a break point on cellForRowAtIndexPath delegate function and see if it comes in this functions, and make sure you are using this function to create your custom cells.
Double check your have at least one row in your tableView using something similar:
- (NSInteger)numberOfRowsInSection:(NSInteger)section
{
return 5; // for test pass value 5 so tableView should aware to display 5 rows.
}
Hope it helps!

How to use UITableViewHeaderFooterView?

Hi I want to use UITableHeaderFooterView in my app and i am doing this:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
[_tableView registerClass:[M3CTableViewCell class] forCellReuseIdentifier:#"cell"];
[_tableView registerClass:[M3CHeaderFooter class] forHeaderFooterViewReuseIdentifier:#"footer"];
}
- (UITableViewHeaderFooterView *)footerViewForSection:(NSInteger)section {
M3CHeaderFooter * footer = [[M3CHeaderFooter alloc]initWithReuseIdentifier:#"footer"];
footer.textLabel.text = #"Test";
return footer;
}
By doing this I am not getting anything at Footer's place.
And this method is not even getting called but I think this method is part of UITableViewDelegate protocol.
Using the new iOS 6 feature of reusable header/footer views involves two steps. You seem to be doing only the first step.
First step: you're telling the table view what class to use for the section header view, by registering your custom subclass of UITableViewHeaderFooterView (I assume your M3CHeaderFooter is a subclass of UITableViewHeaderFooterView).
Second step: Tell the table view what view to use (AND reuse) for a header section by implementing the tableView delegate method
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
So in your viewDidLoad you'd implement something like this:
// ****** Do Step One ******
[_tableView registerClass:[M3CHeaderFooter class] forHeaderFooterViewReuseIdentifier:#"TableViewSectionHeaderViewIdentifier"];
Then you'd implement the table View delegate method in the class where you're creating and displaying your table view:
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return 40.0;
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
static NSString *headerReuseIdentifier = #"TableViewSectionHeaderViewIdentifier";
// ****** Do Step Two *********
M3CHeaderFooter *sectionHeaderView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:headerReuseIdentifier];
// Display specific header title
sectionHeaderView.textLabel.text = #"specific title";
return sectionHeaderView;
}
Now mind you that you do not need to subclass UITableViewHeaderFooterView in order to use it.
Before iOS 6, if you wanted to have a header view for a section, you'd implement the above tableView delegate method and tell the table view what view to use for each section. So each section had a different instance of a UIView which you provided. This means that if your tableView had 100 sections, and inside the delegate method you created a new instance of a UIView, then you would have given the tableView 100 UIViews for the 100 section headers that were displayed.
Using the new feature of reusable header/footer views, you create an instance of a UITableViewHeaderFooterView and the system reuses it for each displayed section header.
If you wanted to have a reusable UITableViewHeaderFooterView without subclassing then you simply change your viewDidLoad to this:
// Register the class for a header view reuse.
[_buttomTableView registerClass:[UITableViewHeaderFooterView class] forHeaderFooterViewReuseIdentifier:#"TableViewSectionHeaderViewIdentifier"];
and then your delegate method to this:
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return 40.0;
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
static NSString *headerReuseIdentifier = #"TableViewSectionHeaderViewIdentifier";
// Reuse the instance that was created in viewDidLoad, or make a new one if not enough.
UITableViewHeaderFooterView *sectionHeaderView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:headerReuseIdentifier];
sectionHeaderView.textLabel.text = #"Non subclassed header";
return sectionHeaderView;
}
I hope that was clear enough.
EDIT: When subclassing the header view, you can implement code similar to the following if you wish to add a custom view to the headerView:
// Add any optional custom views of your own
UIView *customView = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 50.0, 30.0)];
[customView setBackgroundColor:[UIColor blueColor]];
[sectionHeaderView.contentView addSubview:customView];
Doing this in the subclass, as opposed to viewForHeaderInSection: delegate method (as noted below by Matthias), will ensure that only one instance of any subviews are created. You can then add any properties within the subclass that will allow you to access your custom subview.
UITableViewHeaderFooterView is one of the few places I would programmatically handle the view rather than use Storyboard or a XIB. Since you cannot officially use appearance proxy and there is no IB way to do it without abusing UITableViewCells. I do it the old-fashioned way and just use the tag on the label to fetch the custom elements.
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
UITableViewHeaderFooterView *headerView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:kSectionHeaderReuseIdentifier];
if (headerView == nil) {
[tableView registerClass:[UITableViewHeaderFooterView class] forHeaderFooterViewReuseIdentifier:kSectionHeaderReuseIdentifier];
headerView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:kSectionHeaderReuseIdentifier];
}
UILabel *titleLabel = (UILabel *)[headerView.contentView viewWithTag:1];
if (titleLabel == nil) {
UIColor *backgroundColor = [UIColor blackColor];
headerView.contentView.backgroundColor = backgroundColor;
titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(10.0f, 0.0f, 300.0f, 44.0f)];
titleLabel.textColor = [UIColor whiteColor];
titleLabel.backgroundColor = backgroundColor;
titleLabel.shadowOffset = CGSizeMake(0.0f, 0.0f);
titleLabel.tag = 1;
titleLabel.font = [UIFont systemFontOfSize:24.0f];
[headerView.contentView addSubview:titleLabel];
}
NSString *sectionTitle = [self.sections objectAtIndex:section];
if (sectionTitle == nil) {
sectionTitle = #"Missing Title";
}
titleLabel.text = sectionTitle;
return headerView;
}
This is an old post and has good answers, but I wanted to share another work-around for a very similar issue I experienced.
At first, I used:
-(UIView *) tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
With a custom prototype cell for my header view. Subclassing UITableViewCell as such
static NSString *cellIdentifier = #"CustomHeaderCell";
CustomHeaderCell * cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
However, when animating TableView cells above section headers (making them twice as tall) the header view would disappear. This, as pointed out, is because the implementation only supplied a view, not a re-usable view.
Instead of forgoing everything with the customized prototype cell, I implemented the UITableViewHeaderFooterWithIdentifier and set it as the prototyped cell's contentView, without subclassing UITableViewHeaderFooterWithIdentifier.
static NSString *customHeaderViewIdentifier = #"CustomHeaderView";
UITableViewHeaderFooterView *headerView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:customHeaderViewIdentifier];
headerView = (UITableViewHeaderFooterView *)cell.contentView;
I realize this creates two instances of the header view (at least I think it would..) however it does allow you to keep the benefits of a customized prototype cell without doing everything programatically.
Full code:
// viewDidLoad
[myTableView registerClass:[UITableViewHeaderFooterView class] forHeaderFooterViewReuseIdentifier:#"CustomHeaderView"];
// Implement your custom header
-(UIView *) tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
static NSString *cellIdentifier = #"CustomHeaderCell";
CustomHeaderCell * cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
static NSString *customHeaderViewIdentifier = #"CustomHeaderView";
UITableViewHeaderFooterView *headerView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:customHeaderViewIdentifier];
// do your cell-specific code here
// eg. cell.myCustomLabel.text = #"my custom text"
headerView = (UITableViewHeaderFooterView *)cell.contentView;
return headerView;
}
There are a few ways of approaching this, but here is one a solution in Swift: the idea here is that we have a subclass of UITableViewHeaderFooterView called SNStockPickerTableHeaderView; it exposes a method called, configureTextLabel() that when called, sets the font and the color of the text label. We call this method only after the title has been set, that is from, willDisplayHeaderView, and the font gets correctly set.
The header view also supports a custom line separator to set it apart from the rest of the cells.
// MARK: UITableViewDelegate
func tableView(tableView:UITableView, willDisplayHeaderView view:UIView, forSection section:Int) {
if let headerView:SNStockPickerTableHeaderView = view as? SNStockPickerTableHeaderView {
headerView.configureTextLabel()
}
}
func tableView(tableView:UITableView, viewForHeaderInSection section:Int) -> UIView? {
var headerView:SNStockPickerTableHeaderView? = tableView.dequeueReusableHeaderFooterViewWithIdentifier(kSNStockPickerTableHeaderViewReuseIdentifier) as? SNStockPickerTableHeaderView
if (headerView == nil) {
// Here we get to customize the section, pass in background color, text
// color, line separator color, etc.
headerView = SNStockPickerTableHeaderView(backgroundColor:backgroundColor,
textColor:primaryTextColor,
lineSeparatorColor:primaryTextColor)
}
return headerView!
}
And here is the custom UITableViewHeaderFooterView:
import Foundation
import UIKit
private let kSNStockPickerTableHeaderViewLineSeparatorHeight:CGFloat = 0.5
private let kSNStockPickerTableHeaderViewTitleFont = UIFont(name:"HelveticaNeue-Light", size:12)
let kSNStockPickerTableHeaderViewReuseIdentifier:String = "stock_picker_table_view_header_reuse_identifier"
class SNStockPickerTableHeaderView: UITableViewHeaderFooterView {
private var lineSeparatorView:UIView?
private var textColor:UIColor?
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// We must implement this, since the designated init of the parent class
// calls this by default!
override init(frame:CGRect) {
super.init(frame:frame)
}
init(backgroundColor:UIColor, textColor:UIColor, lineSeparatorColor:UIColor) {
super.init(reuseIdentifier:kSNStockPickerTableHeaderViewReuseIdentifier)
contentView.backgroundColor = backgroundColor
self.textColor = textColor
addLineSeparator(textColor)
}
// MARK: Layout
override func layoutSubviews() {
super.layoutSubviews()
let lineSeparatorViewY = CGRectGetHeight(self.bounds) - kSNStockPickerTableHeaderViewLineSeparatorHeight
lineSeparatorView!.frame = CGRectMake(0,
lineSeparatorViewY,
CGRectGetWidth(self.bounds),
kSNStockPickerTableHeaderViewLineSeparatorHeight)
}
// MARK: Public API
func configureTextLabel() {
textLabel.textColor = textColor
textLabel.font = kSNStockPickerTableHeaderViewTitleFont
}
// MARK: Private
func addLineSeparator(lineSeparatorColor:UIColor) {
lineSeparatorView = UIView(frame:CGRectZero)
lineSeparatorView!.backgroundColor = lineSeparatorColor
contentView.addSubview(lineSeparatorView!)
}
}
Here is the result, see section header for, "Popular Stocks":
I can't comment under Cameron Lowell Palmer post but to answer Christopher King, there is a simple way to ensure the re-use without sub-classing UITableViewHeaderFooterView and yet still using custom subviews.
First, do NOT register the class for a header view reuse.
Then in tableView:viewForHeaderInSection: you simply have to create UITableViewHeaderFooterView when needed:
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
static NSString *kYourTableViewReusableHeaderIdentifier = #"ID";
UILabel *titleLabel = nil;
UITableViewHeaderFooterView *headerView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:kYourTableViewReusableHeaderIdentifier];
if (headerView == nil) {
headerView = [[UITableViewHeaderFooterView alloc] initWithReuseIdentifier:kYourTableViewReusableHeaderIdentifier];
titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(...)];
titleLabel.tag = 1;
// ... setup titleLabel
[headerView.contentView addSubview:titleLabel];
} else {
// headerView is REUSED
titleLabel = (UILabel *)[headerView.contentView viewWithTag:1];
}
NSString *sectionTitle = (...); // Fetch value for current section
if (sectionTitle == nil) {
sectionTitle = #"Missing Title";
}
titleLabel.text = sectionTitle;
return headerView;
}
Here is a "quick-and-dirty" way to get this going. It will make a small blue label in the header. I've confirmed that this renders OK in iOS 6 and iOS 7.
in your UITableViewDelegate:
-(void)viewDidLoad
{
...
[self.table registerClass:[UITableViewHeaderFooterView class] forHeaderFooterViewReuseIdentifier:#"Header"];
...
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return 34.;
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
UITableViewHeaderFooterView *header = [tableView dequeueReusableHeaderFooterViewWithIdentifier:#"Header"];
UILabel *leftlabel = [[UILabel alloc] initWithFrame:CGRectMake(0., 0., 400., 34.)];
[leftlabel setBackgroundColor:[UIColor blueColor]];
[header.contentView addSubview:leftlabel];
return header;
}
In case it gets lost in the thorough answers above, the thing that people are likely missing (compared to the standard cellForRowAtIndexPath: method) is that you must register the class used for the section header.
[tableView registerClass:[UITableViewHeaderFooterView class] forHeaderFooterViewReuseIdentifier:#"SectionHeader"];
Try adding registerClass:forHeaderFooterViewReuseIdentifier: and see if it starts working.
One of the reasons that method may not be being called is the style of the table. Standard vs Grouped handles headers/footers differently. That may explain why it's not getting called.
Set delegate property of UITableView instance to reference to the controller that implements next methods:
Method that returns view of section footer:
Asks the delegate for a view object to display in the footer of the specified section of the table view.
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section
Height of view in section footer:
Asks the delegate for the height to use for the footer of a particular section.
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section

Resources