So I'm trying to get the UITableView section headers to change in background and text color when switching from one segment to another on a segmented control. To so so I need to reload the tableView data but I only want to reload the section headers. When using
tableView.reloadData()
It works but it also resets the rest of the data in the tableView which I'm not trying to do.
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let stringForThemeHeader = "Theme Settings"
let stringForToggleHeader = "Password Settings"
if section == 0 {
return stringForThemeHeader
} else {
return stringForToggleHeader
}
}
That's the code for setting up the titles.
override func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
let header = view as! UITableViewHeaderFooterView
if darkModeSegmentedControlSwitcher.selectedSegmentIndex == 1 || darkModeSegmentedControlSwitcher.selectedSegmentIndex == 2 {
view.tintColor = UIColor.red
header.textLabel?.textColor = UIColor.white
} else {
view.tintColor = UIColor.init(red: 247.0/255.0, green: 247.0/255.0, blue: 247.0/255.0, alpha: 1.0)
header.textLabel?.textColor = UIColor.black
}
}
and that's the code to change the text and background. After a segment is selected it doesn't change unless the data of the entire tableView is reloaded using
tableView.reloadData()
So I just need to know how to reload only the section headers and my problem will be solved.
You can just manually iterate the section headers.
for section in 0..<tableView.numberOfSections {
guard let header = tableView.headerView(forSection: section) else {
continue
}
// do something with the header
}
In case you have a lot of the sections you can iterate only visible headers. Just use tableView.visibleCells to collect the visible sections indexes.
Related
I have a tableview which has 2 sections and some cells(which can be dynamic)below each section showing associated data.
This is the code I have written to show the data...
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return recentUsers?.count
} else {
return groupNameArray?.count
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if section == 0 {
return " CHAT LIST"
} else {
return " GROUPS"
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! RecentMessageCell
cell.tag = indexPath.row
if indexPath.section == 0 {
if let user = recentChatUsers?[indexPath.row] {
cell.idLabel?.text = user.id
}
} else {
if groupNameArray.isEmpty == false {
let grpArr = groupNameArray[indexPath.row]
cell.userNameLabel?.text = grpArr.grpname
}
}
return cell
}
Now what I want to achieve is if I click on the first section, it should expand and show the cells it contains and the same should happen with the second cell also. Clicking on each of those sections again should hide the cells that were expanded.
I did search the internet for solutions. But though there were resources available, I couldn't find much help for my problem...
Add an array to keep track of section expend/collapse
let sectionStats = [Bool](repeating: true, count: 2)
Add a, IBAction to track section tap, update value of sectionStats for the corresponding section and reload section
and update your numberOfRowsInSection as show below
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard sectionStats[section] else {
return 0
}
if section == 0 {
return 1
} else {
return list.count
}
}
Tappable Header:
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
return headerView(section: section, title: section == 0 ? "CHAT LIST" : "GROUPS")
}
private func headerView(section: Int, title: String) -> UIView {
let button = UIButton(frame: CGRect.zero)
button.tag = section
button.setTitle(title, for: .normal)
button.setTitleColor(UIColor.red, for: .normal)
button.addTarget(self, action: #selector(sectionHeaderTapped), for: .touchUpInside)
return button
}
#objc private func sectionHeaderTapped(sender: UIButton) {
let section = sender.tag
sectionStats[section] = !sectionStats[section]
tableView.beginUpdates()
tableView.reloadSections([section], with: .automatic)
tableView.endUpdates()
}
Good tutorial on How to build a Table View with Collapsible Sections:
https://medium.com/ios-os-x-development/ios-how-to-build-a-table-view-with-collapsible-sections-96badf3387d0
This kind of feature requires a bit more code and I cannot write the whole code here but I can explain you the concepts that will be used to achieve this and will attach a few good tutorials which I used to ultimately create a feature like this
First you need to create a custom HeaderView for your sections
Next you need a UITapGestureRecognizer on your section and need to write your login inside the function provided in action part of UITapGestureRecognizer's constructor
You need to create a separate Protocol inside your HeaderView file and your ViewController that contains your TableView will adopt to that protocol and will handle whether to expand or collapse your rows
Also, you will need to create a separate Struct instance for each section which will contain a boolean variable that will indicate whether that section is expanded or collapsed
That is the basic concept that will be needed while creating Expandable List in iOS.
Below I have attached links to some of the tutorials :
Tutorial 1
Tutorial 2
Tutorial 3
Tutorial 4
I have created a table view programatically, below is the code, when I tried to update the label using a completion handler the value is not displayed in the table view cell.
Could someone please suggest me how I could update the textLabel in the languagesSpokenCell in my tableview ? I have spent several hours trying to get this resolved, but I am still unable to.
Here is the completion Handler that is called after the user selects the languages that he speaks.
func showLanguagesSpoken(_ languagesSpoken: [String]?){
languagesSpokenString = (languagesSpoken?.joined(separator: ", "))!
languagesSpokenCell.textLabel?.text = languagesSpokenString
//*** In the below print statement the value is printed correctly but the text label is not updated in the cell.
print("languagesSpokenCell.textLabel?.text: \(languagesSpokenCell.textLabel?.text)")
self.tableView.reloadData()
}
Here I am Programatically creating table view cells
// FOR TABLE VIEW - Tableview cells
var tableView: UITableView = UITableView()
var firstNameCell: UITableViewCell = UITableViewCell()
var lastNameCell: UITableViewCell = UITableViewCell()
var languagesSpokenCell: UITableViewCell = UITableViewCell()
// FOR TABLE VIEW - Textfields
var firstName: UITextField = UITextField()
var lastName: UITextField = UITextField()
override func loadView() {
super.loadView()
// construct first name cell, section 0, row 0
self.firstNameCell.backgroundColor = UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 0.5)
self.firstName = UITextField(frame: self.firstNameCell.contentView.bounds.insetBy(dx: 15, dy: 0))
self.firstName.placeholder = "First Name"
self.firstNameCell.addSubview(self.firstName)
// construct last name cell, section 0, row 1
self.lastNameCell.backgroundColor = UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 0.5)
self.lastName = UITextField(frame: self.lastNameCell.contentView.bounds.insetBy(dx: 15, dy: 0))
self.lastName.placeholder = "Last Name"
self.lastNameCell.addSubview(self.lastName)
self.languagesSpokenCell.textLabel?.text = "Languages Spoken"
self.languagesSpokenCell.backgroundColor = UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 0.5)
self.languagesSpokenCell.accessoryType = UITableViewCellAccessoryType.disclosureIndicator
}
Below are the Table view methods
func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
// Return the number of rows for each section in your static table
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch(section) {
case 0: return 2 // section 0 has 2 rows
case 1: return 1 // section 1 has 1 row
default: fatalError("Unknown number of sections")
}
}
// Return the row for the corresponding section and row
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch(indexPath.section) {
case 0:
switch(indexPath.row) {
case 0: return self.firstNameCell // section 0, row 0 is the first name
case 1: return self.lastNameCell // section 0, row 1 is the last name
default: fatalError("Unknown row in section 0")
}
case 1:
switch(indexPath.row) {
case 0: return self.languagesSpokenCell
}
default: fatalError("Unknown section")
}
}
// Customize the section headings for each section
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
switch(section) {
case 0: return "Profile"
case 1: return "Languages Spoken"
default: fatalError("Unknown section")
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let row = indexPath.row
let section = indexPath.section
let currentCell = tableView.cellForRow(at: indexPath) as! UITableViewCell
if section == 4 && row == 0 {
// The user has clicked on languages spoken cell
self.performSegue(withIdentifier: "showLanguageSelectionTVC", sender: self)
}
print("Printing celll text label: \(currentCell.textLabel!.text)")
}
Below are the constraints that I have set in the ViewDidLoad() method.
tableView = UITableView(frame: CGRect.zero, style: UITableViewStyle.grouped)
tableView.delegate = self
tableView.dataSource = self
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
self.view.addSubview(self.tableView)
// Disabling automatic constraints
tableView.translatesAutoresizingMaskIntoConstraints = false
// Do any additional setup after loading the view.
let viewDict = [
"tableView" : tableView
]
// Setting Constraints for the table view
self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[tableView]|", options: [], metrics: nil, views: viewDict))
self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[tableView]|", options: [], metrics: nil, views: viewDict))
To update any cell in tableview you need to reload it. You can reload it per cell or or specific cells or whole tableview. Before reloading tableview make sure that you update your datasource.
In this showLanguagesSpoken method you are trying to change the text of cell without updating the tableView.
Also make sure you always changes the data in cellForRowAtIndexpath
Remove reloadData from following method.
func showLanguagesSpoken(_ languagesSpoken: [String]?){
languagesSpokenString = (languagesSpoken?.joined(separator: ", "))!
languagesSpokenCell.textLabel?.text = languagesSpokenString
//*** In the below print statement the value is printed correctly but the text label is not updated in the cell.
print("languagesSpokenCell.textLabel?.text: \(languagesSpokenCell.textLabel?.text)")
}
Label languagesSpokenCell.textLabel text update process should be on main thread.
func showLanguagesSpoken(_ languagesSpoken: [String]?) {
DispatchQueue.main.async {
languagesSpokenString = (languagesSpoken?.joined(separator: ", "))!
languagesSpokenCell.textLabel?.text = languagesSpokenString
print("languagesSpokenCell.textLabel?.text: \(languagesSpokenCell.textLabel?.text)")
}
}
With Swift 3, I am using a subclass of UITableViewHeaderFooterView (called HeaderView) for the header sections on my TableView.
After dequeueing HeaderView, I customise it by
(1) setting the textLabel.textColor = UIColor.red, and (2) adding a subview to it.
When the application first loads, the table view loads up the headers but they have (what I assume is) the 'default' view (with textLabel.textColor being grey, and without my added subview). When I start scrolling and it starts dequeueing more HeaderViews, then the HeaderViews start coming up correctly, until there are eventually no more 'default' formatted HeaderViews.
Subsequent loads of the app no longer shows the 'default' view.
Alternatives considered
I know that this could be done by making my HeaderView a subclass of
UITableViewCell and customising it from the Storyboard, but that
seems like more of a workaround to use a prototype cell when there is
a UITableViewHeaderFooterView class that was designated for headers
Similarly it could be done with a XIB file, but even in Xcode 8 when
creating a subclass of UITableViewHeaderFooterView it doesn't allow
you to create an XIB file (so there must be some reason..)
Any comments/answers explaining why this is happening and how to resolve it are really appreciated!
UPDATE
As requested I've added in the code to show what I've done- you can recreate the problem with the code below and the usual setting up a TableViewController in the Storyboard (Swift 3, Xcode 8.2, Simulating on iOS 10.2 for iPhone 7)
ListTableViewController.swift
import UIKit
class ListTableViewController: UITableViewController {
// List of titles for each header
var titles: [String] {
var titles = [String]()
for i in 1...100 {
titles.append("List \(i)")
}
return titles as [String]
}
// Register view for header in here
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(ListHeaderView.self, forHeaderFooterViewReuseIdentifier: "Header")
}
// Table view data source
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let dequeuedCell = tableView.dequeueReusableHeaderFooterView(withIdentifier: "Header")
if let cell = dequeuedCell as? ListHeaderView {
cell.title = titles[section]
}
return dequeuedCell
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 44
}
override func numberOfSections(in tableView: UITableView) -> Int {
return titles.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 0
}
}
ListHeaderView.swift
import UIKit
class ListHeaderView: UITableViewHeaderFooterView {
var title: String? {
didSet {
updateUI()
}
}
private func updateUI() {
textLabel?.textColor = UIColor.red
textLabel?.text = title!
let separatorFrame = CGRect(x: 0, y: frame.height-1, width: frame.width, height: 0.25)
let separator = UIView(frame: separatorFrame)
separator.backgroundColor = UIColor.red
contentView.addSubview(separator)
}
}
Here is a screen shot of when the grey headers (screen is full of them upon initial load) and the customised red headers which start to appear upon scrolling.
For anyone interested, seems like this is a bug for which the best solution at this stage is to configure properties such as textColor on the header view in the tableView delegate method willDisplayHeaderView. Doing so 'last minute' just before the view appears allows you to override whatever configurations the system tries to force on the font etc.
Credit to answer found here
Troubles with changing the font size in UITableViewHeaderFooterView
Use this below code
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let dequeuedCell : ListHeaderView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "Header") as? ListHeaderView
cell.title = titles[section]
cell.tittle.textcolor = uicolor.red
return dequeuedCell
}
I changed a tableView from Plain to Grouped so that the header/footer does not float over the table and stays anchored to the top and bottom of the table. That was straightforward, but now the font style formatting that I setup is not working. Strangely all other formatting of the header/footer seems to be working though. Any thoughts on what is going and what I am missing are appreciated!
Code below:
// Setup format of the header
func tableView(tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
let title = UILabel()
title.font = UIFont(name: "Avenir Book", size: 12)
title.textColor = UIColor.whiteColor()
let header: UITableViewHeaderFooterView = view as! UITableViewHeaderFooterView
header.contentView.backgroundColor = UIColor(red: 30/255, green: 30/255, blue: 50/255, alpha: 1)
header.textLabel!.font = title.font
header.textLabel?.textColor = title.textColor
header.textLabel?.numberOfLines = 0
header.textLabel?.lineBreakMode = NSLineBreakMode.ByWordWrapping
header.textLabel?.textAlignment = NSTextAlignment.Center
}
In a Plain table all of the above works great and looks like this:
However, when I change to Grouped table all of the formatting seems to show up except for the font style this this:
I am puzzled about where the ALL CAPS is coming from.
I tried to implement the solution from this question/answer, but could not get it to work either. Thanks for your ideas!
Assuming that you provided the string for the section title in this method:
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
But in a grouped tableview, that string is changed to all caps. To remove the all caps, try adding one of these two lines in the willDisplayHeaderView method:
header.textLabel?.text = header.textLabel!.text!.capitalizedString
header.textLabel?.text = header.textLabel!.text!.lowercaseString
The first one will capitalize the first letter of every word and the second will make everything lowercase. If you don't want either of those you could add the string directly:
header.textLabel?.text = "Here are a few options for you. Select to learn more"
Swift 4
use this delegates:
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let label: UILabel = {
let label = UILabel()
label.textAlignment = .right
label.textColor = .white
label.backgroundColor = .clear
label.font = UIFont.systemFont(ofSize: 20)
return label
}()
return label
}
and don't forget to set height for header:
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 70
}
hope this help.
Update for >=iOS 14, with the UIListContentConfiguration API:
override func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
guard let headerView = view as? UITableViewHeaderFooterView else {
return
}
var config = headerView.defaultContentConfiguration()
// This needs to explicitly be set if the table view is Grouped style
// (which it is, to hide sticky backgrounds on section headers),
// as the deafult style for
config.textProperties.transform = .none
// Other properties (color, font) as needed...
// Notably, `ContentConfiguration` requires setting content here as well,
// separate from `titleForHeaderInSection`
config.text = "MyText"
headerView.contentConfiguration = config
headerView.setNeedsUpdateConfiguration()
}
I have two prototype cells. One for displaying data & other I am using for header view.
When I try to delete my regular cell. The header cell moves with it.
I don't want header to move when I try to delete regular cells.
I have disable user interactions on header prototype cell. Still it keeps moving. In commit editing style I do immediate return for header prototype cell. Still it keeps moving. I don't know what else to do. Please help.
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
if tableView == upcomingTableView {
if let headerCell = tableView.dequeueReusableCellWithIdentifier("headerCell") {
headerCell.textLabel?.text = sectionNames[section]
headerCell.detailTextLabel?.text = "Rs\(sum[section])"
headerCell.detailTextLabel?.textColor = UIColor.blackColor()
headerCell.backgroundColor = UIColor.lightGrayColor()
return headerCell
}
}
return nil
}
cell for row at index path is also very regular code.
Updated Answer:
Create an UIView and add tableViewCell as subview and return the UIView.
Solution in Swift:
override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
if let headerCell = tableView.dequeueReusableCellWithIdentifier("headerCell") {
//Create an UIView programmatically
let headerView = UIView()
headerCell.textLabel?.text = (section == 0) ? "Apple" : "Microsoft"
headerCell.backgroundColor = UIColor.lightGrayColor()
//Add cell as subview to UIView
headerView.addSubview(headerCell)
//Return the header view
return headerView
}
return nil
}
Output: