TableView Header/Footer Font in Grouped Table (Swift) - ios

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

Related

How do I reload just the tableView section headers?

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.

How to adjust left and right insets or margins for UITableView tableHeaderView?

I have a "plain" style UITableView. I am setting a view as the tableViewHeader for the table view. The table also shows the section index down the right side.
My issue is figuring out how to inset the left and right edge of the header view to take into account safe area insets if run on an iPhone X (in landscape) and the table view's section index (if there is one).
I created a simple test app that adds a few dummy rows, a section header, and the section index.
Here is my code for creating a simple header view using a UILabel. My real app won't be using a label but a custom view.
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 30)
label.backgroundColor = .green
label.text = "This is a really long Table Header label to make sure it is working properly."
label.sizeToFit()
tableView.tableHeaderView = label
Without any special attempts to fix the left and right edges, the result in the iPhone X simulator is as follows:
Portait:
Landscape:
Note that without any extra effort, the cells and section header get the desired insets but the header view does not.
I'd like the left edge of the header view to line up with the left edge of the section header and the cells.
I'd like the right edge of the header view to stop where it meets the left edge of the section index. Note that the portrait picture seems like it is already do that but if you look close you can tell the header view goes all the way to the right edge of the table view. You can't see the third . of the ellipses and you can barely see the green behind the section title view.
One attempt I've made was to add the following to my table view controller:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if let header = tableView.tableHeaderView {
var insets = header.layoutMargins
insets.left = tableView.layoutMargins.left
insets.right = tableView.layoutMargins.right
header.layoutMargins = insets
}
}
That code has no effect.
What properties do I set to ensure the header view's left and right edges are indented as needed? Are there constraints that should be applied?
Please note that I'm doing everything in code. So please don't post any solutions that require storyboards or xib files. Answers in either Swift or Objective-C are welcome.
For anyone that wants to replicate this, create a new single view project. Adjust the main storyboard to use a UITableViewController instead of a plan UIViewController and use the following for ViewController:
import UIKit
class ViewController: UITableViewController {
// MARK: - UITableViewController methods
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = "Row \(indexPath.row)"
cell.accessoryType = .disclosureIndicator
return cell
}
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return "Section Header"
}
override func sectionIndexTitles(for tableView: UITableView) -> [String]? {
let coll = UILocalizedIndexedCollation.current()
return coll.sectionIndexTitles
}
override func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int {
return index
}
// MARK: - UIViewController methods
override func viewDidLoad() {
super.viewDidLoad()
tableView.sectionIndexMinimumDisplayRowCount = 1
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 30)
label.backgroundColor = .green
label.text = "This is a really long Table Header label to make sure it is working properly."
label.sizeToFit()
tableView.tableHeaderView = label
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if let header = tableView.tableHeaderView {
var insets = header.layoutMargins
insets.left = tableView.layoutMargins.left
insets.right = tableView.layoutMargins.right
header.layoutMargins = insets
}
}
}
For UITableViews without section index:
label.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
label.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
label.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor)
])
For UITableViews with section index:
label.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
label.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
label.trailingAnchor.constraint(equalTo: tableView.layoutMarginsGuide.trailingAnchor)
])
You need to add some Auto Layout constraints to the label after you add it to the tableview:
…
tableView.tableHeaderView = label
//Add this
label.translatesAutoresizingMaskIntoConstraints = false
label.leadingAnchor.constraint(equalTo: (label.superview?.safeAreaLayoutGuide.leadingAnchor)!).isActive = true
label.trailingAnchor.constraint(equalTo: (label.superview?.safeAreaLayoutGuide.trailingAnchor)!).isActive = true
Also, if you want to see all the text in the label use label.numberOfLines = 0.
You can get rid of the code you added to viewDidLayoutSubviews.
Update:
For fun I did some experimenting in a playground and found that using the layoutMarginsGuide didn't push the trailing edge of the header label far enough over (I'm thinking it comes out right on iPhone X but maybe not on all devices, or the playground behaves a bit differently). I did find though that for table views with at least one cell already in place I could use aCell.contentView.bounds.width, subtract the table view's width and divide the result by two and the header label would sit very nicely next to the section index. As a result I wrote a helper function for setting up constraints. The table view is optional so the function can be used with any view that has a superview and needs to keep inside the safe area. If a table view is passed in it can have a section index or not but it does need to have at least one cell at row 0 section 0:
func constrain(view: UIView, inTableView aTableView: UITableView?) {
guard let container = view.superview else {
print("Constrain error! View must have a superview to be constrained!!")
return
}
view.translatesAutoresizingMaskIntoConstraints = false
view.leadingAnchor.constraint(equalTo: container.safeAreaLayoutGuide.leadingAnchor).isActive = true
if let table = aTableView, let aCell = table.cellForRow(at: IndexPath(row: 0, section: 0)) {
let tableWidth = table.bounds.width
let cellWidth = aCell.contentView.bounds.width
view.trailingAnchor.constraint(equalTo: table.safeAreaLayoutGuide.trailingAnchor, constant: cellWidth - tableWidth).isActive = true
} else {
view.trailingAnchor.constraint(equalTo: container.safeAreaLayoutGuide.trailingAnchor).isActive = true
}
}
I did find one issue when using this. When using a label set to 0 lines with your text it covers the first section header and the first cell of that section. It takes a bit of scrolling to get them out from under the header too. Clipping the label to one line works out quite well though.

Swift: Changing TableView Header text color - Crashing

Using Swift 3, I'm trying to change the Section's Header color programmatically.
How do I change the backgroundColor to white and Text Color to black?
The sections header changes color but no text appears and then when I add code to change the header text color it crashes
Terminating app due to uncaught exception
'NSInvalidArgumentException', reason: 'Can't add self as subview'
Swift Code
// Title for Header
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
// Section Names
let sectionNames = ["Followed Blogs", "All Blogs"]
return sectionNames[section]
}
// View for Header
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = UIView()
let headerLabel = UILabel()
let sectionNames = ["Followed Blogs", "All Blogs"]
headerLabel.text = sectionNames[section]
headerLabel.frame = CGRect(x: 45, y: 5, width: 100, height: 35)
headerLabel.addSubview(headerLabel)
if (section == 0) {
headerView.backgroundColor = UIColor.green
headerLabel.textColor = UIColor.black
} else {
if darkMode {
headerView.backgroundColor = UIColor.white
headerLabel.textColor = UIColor.black
} else {
headerView.backgroundColor = UIColor.black
headerLabel.textColor = UIColor.white
}
}
return headerView
}
// Height for Section
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 45
}
headerLabel.addSubview(headerLabel) is adding the label to self, which is the source of your error
Based on my understanding of your code, you should probably be using headerView.addSubview(headerLabel) instead
The text "Followed Blogs" doesn't fit it shows as "Followed B..."
This is (most likely) a layout issue, I'd personally investigate adding auto layout constraints to the label so that it binds to the top/bottom/left/right margins of the parent view
This is just to add on MadProgrammer's answer. I think instead of UIView you should use UITableViewHeaderFooterView
usage:
tableViewInstance.register(UITableViewHeaderFooterView.self, forHeaderFooterViewResuseIdentifier: "headerIdentifier")
Then in viewForHeaderInSection:
let tableViewHeader = tableview.dequeueReusableHeaderFooterView(withIdentifier: "headerIdentifier")
btw, regarding the text "Followed Blogs" not fitting in its because of your label's width is too small for the current font. Why not add a constraints like this:
headerView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-5-[label]-5-|",
options: [],
metrics: nil,
views: ["tableView": headerLabel]))
headerView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-5-[label]-5-|",
options: [],
metrics: nil,
views: ["tableView": headerLabel]))
You make your tableView's headerHeight be dynamic

UITableViewHeaderFooterView not showing customisations when first loaded

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
}

UITableViewCell won't not receiving touches

I am implementing a view that displays a lot of information. The view is scrollable and inside of the view I implemented a non-scrollable table view holding user comments. I have all the auto-layout constraints and it appears to layout correctly however touches are not received below a certain row. It appears that the table view or something is blocking the views below from receiving the events but I am unable to trace down the issue.
I want the main scroll view's content size to grow as the comment table view grows. Keeping the post comment view at the bottom of the table view. Right now I can't select the last cell or the text field.
Comment Cell View
Simulator screenshot
Here is the code from the table view implementation:
commentsTableView.delegate = self
commentsTableView.dataSource = self
commentsTableView.estimatedRowHeight = 82
commentsTableView.rowHeight = UITableViewAutomaticDimension
commentsTableView.sectionHeaderHeight = UITableViewAutomaticDimension
commentsTableView.estimatedSectionHeaderHeight = 54
commentsTableView.estimatedSectionFooterHeight = 0
commentsTableView.sectionHeaderHeight = UITableViewAutomaticDimension
commentsTableView.tableFooterView = UIView(frame: CGRectZero)
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return "Comments"
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return comments.count
}
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
if section != 0 { return nil }
let sectionTitle: String = self.tableView(tableView, titleForHeaderInSection: section)!
if sectionTitle == "" {
return nil
}
let view = UIView(frame: CGRectMake(0, 0, tableView.frame.width, 54))
let title = UILabel(frame: CGRectMake(60, 22, tableView.frame.width, 17))
view.addSubview(title)
title.text = sectionTitle
title.textColor = UIColor(red: (74 / 255), green: (74 / 255), blue: (74 / 255), alpha: 1.0)
title.backgroundColor = UIColor.clearColor()
view.backgroundColor = UIColor.clearColor()
title.font = UIFont(name: "ProximaNova-Semibold", size: 16.0)
view.layer.addBorder(.Bottom, color: UIColor.lightGrayColor().colorWithAlphaComponent(0.75), thickness: 0.5)
title.setNeedsDisplay()
view.setNeedsDisplay()
return view
}
You have to set the userInteractionEnabled to true to fire those events.
view.userInteractionEnabled = true
The Comment TextField is the last row of your comment TableView. So put comment TextField code in footer of the TableView as follows:
func tableView(tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
let commentTextFieldView = UIView(frame: CGRectMake(0, 0, tableView.frame.size.width, 50))
// Your Comment TextField code
return commentTextFieldView
}
func tableView(tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 50.0
}
There is an another way to assign Comment TextField view as tableView footer:
myTableView.tableFooterView = commentTextFieldView

Resources