I have a static UITableView, content of each cell I've made in storyboard, but I need to change texLabels of some cells programmatically while runtime. How can I do it?
Create a property for each cell you want to change in your table view controller, like so:
#property (weak) IBOutlet UITableViewCell *cell1;
#property (weak) IBOutlet UITableViewCell *cell2;
Connect each one to a cell in Interface Builder.
When you only need to change the label's text, you can use
self.cell1.textLabel.text = #"New Text";
If you need to replace the whole label, use
UILabel *newLabel = [[UILabel alloc] init];
self.cell2.textLabel = newLabel;
#RossPenman has posted a good answer.
#ggrana noted a potential issue to do with memory and cell reuse, however don't worry about that ...
For a UITableView with Static cells all the cells are instantiated up front, before viewDidLoad is called on your UITableViewController and aren't reused in the way that dynamic cells are. As such you can even just take IBOutlets directly to the UITextFields, UISwitches, UILabels and things you are really interested in that you have dropped into you static cells in the Storyboard.
I needed this.
dispatch_async(dispatch_get_main_queue(), ^{
(...textLabel updates)
[self.tableView reloadData];
});
You can get the cell from your table view using the method cellForRowAtIndexPath, you will need to define a outlet that retrieves your tableview.
__weak IBOutlet UITableView *tableView;
after that you can get the cell like that:
NSIndexPath* indexPath = [NSIndexPath indexPathForRow:yourRow inSection:yourSection];
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
[[cell textLabel] setText:#"Your new text"];
Maybe after setting the text you will need to adjust your label, or your cell height, if you want a deeper help provide more information, and I will be glad to help you.
And you are done, hope it helps.
Swift solution:
First make a IBOutlet of static TableViewCell
#IBOutlet weak var cellFirst: UITableViewCell!
then in viewDidLoad change label Name.
cellFirst.textLabel?.text = "Language"
Note: If you have attached a label on TableViewCell then just hide it using storyboard.
Hope it works:
#IBOutlet weak var yourTextField: UITextField!
private var yourText: String?
I just custom in this tableview's delegate:
override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
guard let yourText = yourText else { return }
yourTextField.text = yourText
}
When you change the text:
yourText = "New text here"
tableView.reloadData()
You can do something like this
for (int section = 0; section < [table numberOfSections]; section++) {
for (int row = 0; row < [table numberOfRowsInSection:section]; row++) {
NSIndexPath* cellPath = [NSIndexPath indexPathForRow:row inSection:section];
UITableViewCell* cell = [self cellForRowAtIndexPath:cellPath];
cell.backgroundColor = [UIColor blackColor];
cell.textLabel.textColor = [UIColor whiteColor;
cell.textLabel.text = #"Your text";
}
}
Also, make sure to keep your changes out of viewDidAppear and to place them in viewWillAppear or you will run into problems
Your can try it
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath)
{
cell.textLabel?.text = titles[indexPath.row]
}
Related
I'm just making a simple project for fun but I am having a very strange bug in my program.
This function returns 29 which is correct however it is called three times when my table view is instantiated.
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
print("ingredient count: \(Sushi.ingredients.count)")
return Sushi.ingredients.count
}
And I think that (_: numberOfRowsInSection:) being called 3 times is what leads this code to execute almost three times the number of elements in the ingredients array.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
guard let cell = tableView.dequeueReusableCell(withIdentifier: "IngredientCell", for: indexPath) as? IngredientCell
else
{
return UITableViewCell(style: .default, reuseIdentifier: nil)
}
cell.ingredientLabel.text = Sushi.ingredients[indexPath.row]
print("ingredient label text: \(cell.ingredientLabel.text)\nindex path: \(indexPath)\ncell index: \(cell.index)\nbutton tag: \(cell.selectButton.tag)\nindex path row: \(indexPath.row)\ncell object: \(cell)\n")
return cell
}
The table view is populated appropriately as far as it lists all the ingredients in the array correctly. However, each row of the tableView is a prototype cell that has a UILabel, a UIButton, and a UIImage. When the button is pressed the text and color change for that button but also for every thirteenth button in the tableview cells.
This class is for my individual cells
class IngredientCell: UITableViewCell
{
public var userSelected = false
public var index: Int = 0
#IBOutlet weak var picture: UIImageView!
#IBOutlet weak var selectButton: UIButton!
#IBOutlet weak var ingredientLabel: UILabel!
//This method is called when a button is pressed
#IBAction func selectIngredient(_ sender: UIButton)
{
if userSelected == false
{
userSelected = true
selectButton.setTitleColor(.red, for: .normal)
selectButton.setTitle("Remove", for: .normal)
}
else
{
userSelected = false
selectButton.setTitle("Select", for: .normal)
selectButton.setTitleColor(.blue, for: .normal)
}
}
}
I decided to debug using print statements to see if I could figure out what exactly is going on and I found that numberOfRowsInSection is called 3 times and cellForRowAtIndexPath is called a varying amount of times. I keeps iterating over the table view giving the cell objects the same memory addresses which I suppose is what causes multiple buttons to change when only one is pressed. My console output proved that different buttons in the storyboard had the same addresses in memory.
Sorry, I can only write in Objective-C. But anyway, I am just going to show you the idea.
One of the ways to update the UI contents of just a single item of a table is to reload that specific cell.
For example: Your cell contains just a button. When you press a specific button you want to change the title of that button only. Then all you have to do is to create an dictionary which references all the titles of all buttons in your table, and then set the title to that button upon reload of that specific cell.
You can do it this way:
#interface ViewController ()<UITableViewDelegate, UITableViewDataSource>
#property NSMutableDictionary *buttonTitlesDict;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
buttonTitles = [[NSMutableDictionary alloc] init];
}
(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *identifier = #"SimpleTableItem";
UITableViewCell *tableCell = [tableView dequeueReusableCellWithIdentifier:identifier];
if(tableCell == nil)
{
tableCell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
}
tableCell.button.title = [_buttonTitles objectForKey:#(indexPath.row)];
tableCell.button.tag = indexPath.row;
}
//reloads only a specific cell
- (IBAction)updateButton:(id)sender
{
NSInteger row = [sender tag];
[_buttonTitlesDict setObject:#"changedTitle" forKey:#(row)];
[_tableView reloadRowsAtIndexPaths:#[[NSIndexPath indexPathForRow:row inSection:0]] withRowAnimation:UITableViewRowAnimationNone];
}
As you can see, I just created a dictionary that holds the button title for each button with the index of the cell as the reference key. Every time I want to update the title of the button, I will get that title from the button every time the cell is loaded upon cellForRowAtIndexPath. take note that when you scroll the table, cellForRowAtIndexPath will be called when new cells appear in front of you. So as you scroll, the correct titles will be updated to the buttons.
Hope this helps.
The only thing UITableViewCell contains is one UIButton. So far, I can print the 'currentTitle' of the clicked UIButton on console. What I'd like to do is change the style of the clicked button bold. If another one is clicked, the former one should go back to regular and the new one needs to change bold.
It they were multiple buttons in a UIViewController, I would have easily done this by adding those buttons separately but I don't think this case will be done in that way.
Should I save the index of the selected button in the UIViewController class, and reload the UITableView? If so, can anyone let me know how to handle this? I have this idea but I don't know how to do it.
var selectIndex:NSIndexPath = NSIndexPath(forRow: -1, inSection: 0)
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell: UITableViewCell! = tableView.dequeueReusableCellWithIdentifier("cell")! as UITableViewCell
cell.btn.addTarget(self, action: Selector("ButtonAction:"), forControlEvents: UIControlEvents.TouchUpInside)
cell.btn.tag = indexPath.row
cell.btn.titleLabel?.font = UIFont.systemFontOfSize(20)
if indexPath.row == selectIndex.row
{
cell.btn.titleLabel?.font = UIFont.boldSystemFontOfSize(20)
}
return cell
}
func ButtonAction(sender: UIButton)
{
selectIndex = NSIndexPath(forRow: sender.tag, inSection: 0)
yourTableView.reloadData()
}
Try This code
CustomTableViewCell.h
#property (nonatomic, weak) IBOutlet UIButton *button;
#property (nonatomic, copy) void (^ButtonTapAction)(CustomTableViewCell *aCell);
CustomTableViewCell.m
//Method assign to that button
- (IBAction)arrowButtonTapped:(id)sender
{
if (self.ButtonTapAction) {
self.ButtonTapAction(self);
}
}
cell Used in tableview cellForRowAtIndexpath:
__weak typeof(self) weakSelf = self;
cell.arrowButtonTapAction = ^(CustomTableViewCell *aCell){
aCell.button.titleLabel.font=[UIFont boldSystemFontOfSize:20];
};
By Using this code you dont need to reload Cell.
You can add a property that stores the row that corresponds to the cell with the currently selected button.
In your cellForRowAtIndexPath you check whether indexPath.row == selectedRow and set the button appearance appropriately.
When the selection changes, call reloadCellsAtIndexPaths with the newly selected row and the previously selected row if applicable.
You have to do it in cellForRowAtIndexPath keep a flag saying it has been selected.
Also you have to call cellForRowAtIndexPath on tableView.visibleCells for when the button is selected, because cellForRowAtIndexPath only works when the cell is coming into view.
I have a tableview with 7 custom cells. The cells are big enough that only 5 of them fit on screen at any one time. The different cells are user-configurable in terms of content, which is how I noticed that there is something strange going on. When the table first loads, and you scroll down to view all the cells for the first time, the cell contents are all correct. What is odd, however, is that once you scroll up and down a few times such that the top cells and the bottom cells disappear off screen a couple of times, the content of the bottom cell will pick up properties of the first cell.
Note that there is no code at all in willDisplayCell, so there is no reason for the content to change just due to scrolling. Also, all of the scrolling is without registering a touch in any of the cells. I have a strong feeling this is a dequeuing problem of some sort. What do I need to do to make sure the content of the cells remains stable regardless of the amount of scrolling?
Here is the TVC code:
import Foundation
import UIKit
class adventureTVC: UITableViewController {
var focusArray: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
// General Setup
self.title = selectedGenre
self.tableView.rowHeight = 98.0
self.tableView.separatorStyle = UITableViewCellSeparatorStyle.None
self.view.backgroundColor = UIColor(red: 0.8, green: 0.8, blue: 0.8, alpha: 1.0)
self.tableView.contentInset.bottom = 49
// Registering the custom cell
let nib = UINib(nibName: "StarsAcrossCell", bundle: nil)
tableView.registerNib(nib, forCellReuseIdentifier: "starsCell")
// Data used by this TVC. This should be the Genre for which ever cell was touched to get here
focusArray = bookage.focusArray()
}
override func viewWillAppear(animated: Bool) {
tableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func numberOfSectionsInTableView(tableView: UITableView?) -> Int {
return 1
}
override func tableView(tableView: UITableView?, numberOfRowsInSection section: Int) -> Int {
return focusArray.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: StarsAcrossCell = self.tableView.dequeueReusableCellWithIdentifier("starsCell") as! StarsAcrossCell
cell.selectionStyle = .None
cell.groupTypeLabel.text = focusArray[indexPath.row]
cell.cellBorder.backgroundColor = getCellColor(indexPath.row)
let statusArray = bookage.getReadStatusArrayForFocus(selectedCategory, genre: selectedGenre, focus: focusArray[indexPath.row])
setStarImages(statusArray, cell: cell)
return cell
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
}
}
It seems cell reuse issue. You need to implement -prepareForReuse method in your custom cell class and set all cell properties to default value.
- (void)prepareForReuse
If a UITableViewCell object is reusable—that is, it has a reuse
identifier—this method is invoked just before the object is returned
from the UITableView method dequeueReusableCellWithIdentifier:. For
performance reasons, you should only reset attributes of the cell that
are not related to content, for example, alpha, editing, and selection
state. The table view's delegate in tableView:cellForRowAtIndexPath:
should always reset all content when reusing a cell. If the cell
object does not have an associated reuse identifier, this method is
not called. If you override this method, you must be sure to invoke
the superclass implementation.
Refer here for more, https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableViewCell_Class/#//apple_ref/occ/instm/UITableViewCell/prepareForReuse
First of all create customtableviewcell (if you haven't created it)
RatingViewCell.h
#interface RatingViewCell : UITableViewCell<RatingViewDelegate>
#property (strong, nonatomic) IBOutlet UILabel *lblTitle;
#property (strong, nonatomic) RatingView *ratingView;
#end
RatingViewCell.m
#implementation RatingViewCell
#synthesize lblTitle,ratingView2;
- (void)awakeFromNib
{
// Initialization code
ratingView2 = [[RatingView alloc] initWithFrame:CGRectMake(-10,22,130,15)
selectedImageName:#"RatingStartBig"
unSelectedImage:#"RatingStartBigBlank"
minValue:0
maxValue:5
intervalValue:0.5
stepByStep:NO];
ratingView2.userInteractionEnabled = NO;
ratingView2.delegate = self;
[self.contentView addSubview:ratingView2];
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
Then use this customCell in tableview's CellforRow method as
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"CellID";
RatingViewCell *cell = (RatingViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil){
cell = [[RatingViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier];
}
cell.lblTitle.text = #"Title"; // use array to pass values dynamically
cell.ratingView2.value = 2.0; // use array to pass values dynamically
return cell;
}
I have two UITextFields inside a custom cell of a UITableView.
I need to edit and store values of the textFields.
When I click inside a UITextField I have to know the row it belongs to in order to save the value to the correct record of a local array.
How can I get the row index of the textField?
I tried :
-(void)textFieldDidBeginEditing:(UITextField *)textField
{
currentRow = [self.tableView indexPathForSelectedRow].row;
}
But the currentRow does not change when I click inside the UITextFieldRow.It changes only when I click (select) the entire row...
The text field did not send touch event to the table view so indexPathForSelectedRow is not working. You can use:
CGPoint textFieldOrigin = [self.tableView convertPoint:textField.bounds.origin fromView:textField];
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:textFieldOrigin];
In iOS 8 I found that the simulator and device had different number of superviews, so this is a little more generic and should work across all versions of iOS:
UIView *superview = textField.superview;
while (![superview isMemberOfClass:[UITableViewCell class]]) { // If you have a custom class change it here
superview = superview.superview;
}
UITableViewCell *cell =(UITableViewCell *) superview;
NSIndexPath *indexPath = [self.table indexPathForCell:cell];
Try this
//For ios 7
UITableViewCell *cell =(UITableViewCell *) textField.superview.superview.superview;
NSIndexPath *indexPath = [tblView indexPathForCell:cell];
//For ios 6
UITableViewCell *cell =(UITableViewCell *) textField.superview.superview;
NSIndexPath *indexPath = [tblView indexPathForCell:cell];
1>You can achieve it by programmatically creating textfields in your CellForRowAtIndexPath and setting text field's tag as indexpath.row.
then textFieldDidBeginEditing you can just fetch textField.tag and achieve what you want.
2>another way is to have 2 custom cells in one table view. in that way you cam place text feild individually and set their tags from your utility panel.
What I do is create a custom cell, and put whatever custom UI elements I need inside and create a property indexPath which gets set when the cell is dequeued. Then I pass the indexPath along to the custom elements in didSet.
class EditableTableViewCell: UITableViewCell {
#IBOutlet weak var textField: TableViewTextField!
var indexPath: IndexPath? {
didSet {
//pass it along to the custom textField
textField.indexPath = indexPath
}
}
}
class TableViewTextField: UITextField {
var indexPath: IndexPath?
}
In TableView:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "EditableCell") as! EditableTableViewCell
cell.indexPath = indexPath
return cell
}
Then I implement the UITextFieldDelegate protocol, and since the textField has its indexPath you will always know where it came from.
Not sure where the best place to set the delegate is. The easiest is to set it when the cell is dequeued.
override func textFieldDidEndEditing(_ textField: UITextField) {
guard let myTextField = textField as? TableViewTextField else { fatalError() }
guard let indexPath = myTextField.indexPath else { fatalError() }
}
Without using a storyboard we could simply drag a UIView onto the canvas, lay it out and then set it in the tableView:viewForHeaderInSection or tableView:viewForFooterInSection delegate methods.
How do we accomplish this with a StoryBoard where we cannot drag a UIView onto the canvas
Just use a prototype cell as your section header and / or footer.
add an extra cell and put your desired elements in it.
set the identifier to something specific (in my case SectionHeader)
implement the tableView:viewForHeaderInSection: method or the tableView:viewForFooterInSection: method
use [tableView dequeueReusableCellWithIdentifier:] to get the header
implement the tableView:heightForHeaderInSection: method.
-(UIView *) tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
static NSString *CellIdentifier = #"SectionHeader";
UITableViewCell *headerView = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (headerView == nil){
[NSException raise:#"headerView == nil.." format:#"No cells with matching CellIdentifier loaded from your storyboard"];
}
return headerView;
}
Edit: How to change the header title (commented question):
Add a label to the header cell
set the tag of the label to a specific number (e.g. 123)
In your tableView:viewForHeaderInSection: method get the label by calling:
UILabel *label = (UILabel *)[headerView viewWithTag:123];
Now you can use the label to set a new title:
[label setText:#"New Title"];
I know this question was for iOS 5, but for the benefit of future readers, note that effective iOS 6 we can now use dequeueReusableHeaderFooterViewWithIdentifier instead of dequeueReusableCellWithIdentifier.
So in viewDidLoad, call either registerNib:forHeaderFooterViewReuseIdentifier: or registerClass:forHeaderFooterViewReuseIdentifier:. Then in viewForHeaderInSection, call tableView:dequeueReusableHeaderFooterViewWithIdentifier:. You do not use a cell prototype with this API (it's either a NIB-based view or a programmatically created view), but this is the new API for dequeued headers and footers.
In iOS 6.0 and above, things have changed with the new dequeueReusableHeaderFooterViewWithIdentifier API.
I have written a guide (tested on iOS 9), which can be summarised as such:
Subclass UITableViewHeaderFooterView
Create Nib with the subclass view, and add 1 container view which contains all other views in the header/footer
Register the Nib in viewDidLoad
Implement viewForHeaderInSection and use dequeueReusableHeaderFooterViewWithIdentifier to get back the header/footer
I got it working in iOS7 using a prototype cell in the storyboard. I have a button in my custom section header view that triggers a segue that is set up in the storyboard.
Start with Tieme's solution
As pedro.m points out, the problem with this is that tapping the section header causes the first cell in the section to be selected.
As Paul Von points out, this is fixed by returning the cell's contentView instead of the whole cell.
However, as Hons points out, a long press on said section header will crash the app.
The solution is to remove any gestureRecognizers from contentView.
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
static NSString *CellIdentifier = #"SectionHeader";
UITableViewCell *sectionHeaderView = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
while (sectionHeaderView.contentView.gestureRecognizers.count) {
[sectionHeaderView.contentView removeGestureRecognizer:[sectionHeaderView.contentView.gestureRecognizers objectAtIndex:0]];
}
return sectionHeaderView.contentView; }
If you aren't using gestures in your section header views, this little hack seems to get it done.
If you use storyboards you can use a prototype cell in the tableview to layout your header view. Set an unique id and viewForHeaderInSection you can dequeue the cell with that ID and cast it to a UIView.
If you need a Swift Implementation of this follow the directions on the accepted answer and then in you UITableViewController implement the following methods:
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
return tableView.dequeueReusableCell(withIdentifier: "CustomHeader")
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 75
}
The solution I came up with is basically the same solution used before the introduction of storyboards.
Create a new, empty interface class file. Drag a UIView on to the canvas, layout as desired.
Load the nib manually, assign to the appropriate header/footer section in viewForHeaderInSection or viewForFooterInSection delegate methods.
I had hope that Apple simplified this scenario with storyboards and kept looking for a better or simpler solution. For example custom table headers and footers are straight forward to add.
When you return cell's contentView you will have a 2 problems:
crash related to gestures
you don't reusing contentView (every time on viewForHeaderInSection call, you creating new cell)
Solution:
Wrapper class for table header\footer.
It is just container, inherited from UITableViewHeaderFooterView, which holds cell inside
https://github.com/Magnat12/MGTableViewHeaderWrapperView.git
Register class in your UITableView (for example, in viewDidLoad)
- (void)viewDidLoad {
[super viewDidLoad];
[self.tableView registerClass:[MGTableViewHeaderWrapperView class] forHeaderFooterViewReuseIdentifier:#"ProfileEditSectionHeader"];
}
In your UITableViewDelegate:
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
MGTableViewHeaderWrapperView *view = [tableView dequeueReusableHeaderFooterViewWithIdentifier:#"ProfileEditSectionHeader"];
// init your custom cell
ProfileEditSectionTitleTableCell *cell = (ProfileEditSectionTitleTableCell * ) view.cell;
if (!cell) {
cell = [tableView dequeueReusableCellWithIdentifier:#"ProfileEditSectionTitleTableCell"];
view.cell = cell;
}
// Do something with your cell
return view;
}
I've been in trouble within a scenario where Header was never reused even doing all the proper steps.
So as a tip note to everyone who want to achieve the situation of show empty sections (0 rows) be warn that:
dequeueReusableHeaderFooterViewWithIdentifier will not reuse the header until you return at least one row
Hope it helps
I used to do the following to create header/footer views lazily:
Add a freeform view controller for the section header/footer to the storyboard
Handle all stuff for the header in the view controller
In the table view controller provide a mutable array of view controllers for the section headers/footers repopulated with [NSNull null]
In viewForHeaderInSection/viewForFooterInSection if view controller does not yet exist, create it with storyboards instantiateViewControllerWithIdentifier, remember it in the array and return the view controllers view
Similar to laszlo answer but you can reuse the same prototype cell for both the table cells and the section header cell. Add the first two functions below to your UIViewController subClass
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let cell = tableView.dequeueReusableCell(withIdentifier: "DataCell") as! DataCell
cell.data1Label.text = "DATA KEY"
cell.data2Label.text = "DATA VALUE"
return cell
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 75
}
// Example of regular data cell dataDelegate to round out the example
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "DataCell", for: indexPath) as! PlayerCell
cell.data1Label.text = "\(dataList[indexPath.row].key)"
cell.data2Label.text = "\(dataList[indexPath.row].value)"
return cell
}
To follow up on Damon's suggestion, here is how I made the header selectable just like a normal row with a disclosure indicator.
I added a Button subclassed from UIButton (subclass name "ButtonWithArgument") to the header's prototype cell and deleted the title text (the bold "Title" text is another UILabel in the prototype cell)
then set the Button to the entire header view, and added a disclosure indicator with Avario's trick
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
static NSString *CellIdentifier = #"PersonGroupHeader";
UITableViewCell *headerView = (UITableViewCell *) [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(headerView == nil)
{
[NSException raise:#"headerView == nil, PersonGroupTableViewController" format:[NSString stringWithFormat:#"Storyboard does not have prototype cell with identifier %#",CellIdentifier]];
}
// https://stackoverflow.com/a/24044628/3075839
while(headerView.contentView.gestureRecognizers.count)
{
[headerView.contentView removeGestureRecognizer:[headerView.contentView.gestureRecognizers objectAtIndex:0]];
}
ButtonWithArgument *button = (ButtonWithArgument *)[headerView viewWithTag:4];
button.frame = headerView.bounds; // set tap area to entire header view
button.argument = [[NSNumber alloc] initWithInteger:section]; // from ButtonWithArguments subclass
[button addTarget:self action:#selector(headerViewTap:) forControlEvents:UIControlEventTouchUpInside];
// https://stackoverflow.com/a/20821178/3075839
UITableViewCell *disclosure = [[UITableViewCell alloc] init];
disclosure.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
disclosure.userInteractionEnabled = NO;
disclosure.frame = CGRectMake(button.bounds.origin.x + button.bounds.size.width - 20 - 5, // disclosure 20 px wide, right margin 5 px
(button.bounds.size.height - 20) / 2,
20,
20);
[button addSubview:disclosure];
// configure header title text
return headerView.contentView;
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return 35.0f;
}
-(void) headerViewTap:(UIGestureRecognizer *)gestureRecognizer;
{
NSLog(#"header tap");
NSInteger section = ((NSNumber *)sender.argument).integerValue;
// do something here
}
ButtonWithArgument.h
#import <UIKit/UIKit.h>
#interface ButtonWithArgument : UIButton
#property (nonatomic, strong) NSObject *argument;
#end
ButtonWithArgument.m
#import "ButtonWithArgument.h"
#implementation ButtonWithArgument
#end
You should use Tieme's solution as a base but forget about the viewWithTag: and other fishy approaches, instead try to reload your header (by reloading that section).
So after you sat up your custom cell-header view with all the fancy AutoLayout stuff, just dequeue it and return the contentView after your set up, 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;
}
What about a solution where the header is based on a view array :
class myViewController: UIViewController {
var header: [UILabel] = myStringArray.map { (thisTitle: String) -> UILabel in
let headerView = UILabel()
headerView.text = thisTitle
return(headerView)
}
Next in the delegate :
extension myViewController: UITableViewDelegate {
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
return(header[section])
}
}
Add cell in StoryBoard, and set reuseidentified
Code
class TP_TaskViewTableViewSectionHeader: UITableViewCell{
}
and
Use:
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let header = tableView.dequeueReusableCell(withIdentifier: "header", for: IndexPath.init(row: 0, section: section))
return header
}
Here is #Vitaliy Gozhenko's answer, in Swift.
To summarize you will create a UITableViewHeaderFooterView that contains a UITableViewCell. This UITableViewCell will be "dequeuable" and you can design it in your storyboard.
Create a UITableViewHeaderFooterView class
class CustomHeaderFooterView: UITableViewHeaderFooterView {
var cell : UITableViewCell? {
willSet {
cell?.removeFromSuperview()
}
didSet {
if let cell = cell {
cell.frame = self.bounds
cell.autoresizingMask = [UIViewAutoresizing.FlexibleHeight, UIViewAutoresizing.FlexibleWidth]
self.contentView.backgroundColor = UIColor .clearColor()
self.contentView .addSubview(cell)
}
}
}
Plug your tableview with this class in your viewDidLoad function:
self.tableView.registerClass(CustomHeaderFooterView.self, forHeaderFooterViewReuseIdentifier: "SECTION_ID")
When asking, for a section header, dequeue a CustomHeaderFooterView, and insert a cell into it
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let view = self.tableView.dequeueReusableHeaderFooterViewWithIdentifier("SECTION_ID") as! CustomHeaderFooterView
if view.cell == nil {
let cell = self.tableView.dequeueReusableCellWithIdentifier("Cell")
view.cell = cell;
}
// Fill the cell with data here
return view;
}