How do you change the colour of a section title in a tableview? - ios

Here is what I have at the moment.
How do I refer to this so that I can change the text colour to match my index list? The sectionForSectionIndexTitle worked well for adding in the correct section title but how exactly does one access the title element?
Or is it impossible and I need to redraw the view and add it with viewForHeaderInSection?

you can use the one of UITableViewDelegate's method
swift3 and above
func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
if let headerView = view as? UITableViewHeaderFooterView {
headerView.contentView.backgroundColor = .white
headerView.backgroundView?.backgroundColor = .black
headerView.textLabel?.textColor = .red
}
}
objective C
- (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section
{
if([view isKindOfClass:[UITableViewHeaderFooterView class]]){
UITableViewHeaderFooterView * headerView = (UITableViewHeaderFooterView *) view;
headerView.textLabel.textColor = [UIColor RedColor];
}
}
for Reference I taken the model answer from here

One liner solution (using optional chaining):
override func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
(view as? UITableViewHeaderFooterView)?.textLabel?.textColor = UIColor.red
}

Custom Title:
override func tableView(tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
let title = UILabel()
title.font = UIFont(name: "SFUIDisplay-Light", size: 13)!
title.textColor = UIColor.redColor()
let header = view as! UITableViewHeaderFooterView
header.textLabel!.font=title.font
header.textLabel!.textColor=title.textColor
header.contentView.backgroundColor = UIColor.whiteColor()
}

Swift Solution
func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
view.tintColor = UIColor.red
let header = view as! UITableViewHeaderFooterView
header.textLabel?.textColor = UIColor.white
}

I would use the Appearance() proxy class. I usually add them in a function in AppDelegate and call them didFinishLaunching.
private func setupApperances() {
UILabel.appearance(whenContainedInInstancesOf: [UITableViewHeaderFooterView.self]).textColor = .red
}

You can make your own section title (header/footer) view, and it is easy.
class BlackTableViewHeaderFooterView : UITableViewHeaderFooterView {
override init(reuseIdentifier: String?) {
super.init(reuseIdentifier: reuseIdentifier)
contentView.backgroundColor = .black
textLabel?.font = UIFont.preferredFont(forTextStyle: .body)
textLabel?.numberOfLines = 0
textLabel?.textColor = .white
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class TableViewController : UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(BlackTableViewHeaderFooterView.self, forHeaderFooterViewReuseIdentifier: "\(BlackTableViewHeaderFooterView.self)")
// do other setup
}
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: "\(BlackTableViewHeaderFooterView.self)")
header.textLabel?.text = "" // set your header title
return header
}
}

Related

UITableView Alphabetical Header Sections Determined by a First Letter of a Model Property

I'm trying to create an UITableView with alphabetical order headers depending on my businessName property's first letter of my Company model, and then hide the header sections if there's not a company in the collection that starts with a letter. I'm already fetching the companies into the controller, but I'm having difficulty with limiting my companies into the right section and then hiding the sections that don't have the businessName's first letter.
// Current situation
// What I'm looking for
// Company Model
`struct Company {
var uid: String
var businessAddress: String
var businessName: String
init(dictionary: [String : Any]) {
self.uid = dictionary["uid"] as? String ?? ""
self.businessAddress = dictionary["businessAddress"] as? String ?? ""
self.businessName = dictionary["businessName"] as? String ?? ""
}
}`
// Service
`struct EmployeeService {
static func fetchCompanies(completion: #escaping([Company]) -> Void) {
var companies = [Company]()
let query = REF_COMPANIES.order(by: "businessName")
query.addSnapshotListener { snapshot, error in
snapshot?.documentChanges.forEach({ change in
let dictionary = change.document.data()
let company = Company(dictionary: dictionary)
companies.append(company)
companies.sort {
$0.businessName < $1.businessName
}
completion(companies)
})
}
}
}`
// CompanySelectionController
`class CompanySelectionController: UIViewController {
// MARK: - Properties
var companies = [Company]()
let sectionTitles = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".map(String.init)
}
// MARK: - UITableViewDataSource
extension CompanySelectionController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return sectionTitles.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return companies.count
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
// Create the header view
let headerView = UIView.init(frame: CGRectMake(0, 0, self.view.frame.size.width, 40))
headerView.backgroundColor = UIColor.groupTableViewBackground
// Create the Label
let label = UILabel(frame: CGRectMake(10, -40, 120, 60))
label.font = UIFont(name: "AvenirNext-DemiBold", size: 16)
label.textAlignment = .left
label.text = sectionTitles[section]
// Add the label to your headerview
headerView.addSubview(label)
return headerView
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 5
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifiers.CompanySelectionCell, for: indexPath) as! CompanySelectionCell
cell.selectionStyle = .none
cell.company = companies[indexPath.row]
return cell
}
}`
// CompanySelectionCell
`protocol CompannyViewModelItem {
var type: CompannyViewModelItemType { get }
var rowCount: Int { get }
var sectionTitle: String { get }
}
class CompanySelectionCell: UITableViewCell {
// MARK: - Properties
var company: Company! {
didSet {
configure()
}
}
private let businessNameLabel: UILabel = {
let label = UILabel()
label.font = UIFont(name: "AvenirNext-DemiBold", size: 14)
label.textColor = .black
label.textAlignment = .left
return label
}()
private let businessAddressLabel: UILabel = {
let label = UILabel()
label.font = UIFont(name: "AvenirNext-MediumItalic", size: 12)
label.textColor = .darkGray
label.textAlignment = .left
return label
}()
lazy var businessStack = UIStackView(arrangedSubviews: [businessNameLabel, businessAddressLabel])
// MARK: - Lifecycle
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Helper Functions
fileprivate func configure() {
guard let company = company else { return }
let viewModel = CompannyViewModel(company: company)
businessNameLabel.text = viewModel.businessName
businessAddressLabel.text = viewModel.businessAddress
}
}`
// CompannyViewModel
enum CompannyViewModelItemType { case uid case businessName }
I have tried changing the label property inside viewForHeaderInSection to try and conform to the right letter, but the screenshot of my problem has been the furthest I've got.
Your problem is your datasource for the UITableView. What you're using is actually a static structure with 26 sections (all letters) and you're appending all your companies to every section. That is happening here:
func numberOfSections(in tableView: UITableView) -> Int {
return sectionTitles.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return companies.count
}
To show what you actually want you need a different datastructure like a nested Array to fill your table view.
let dataSource: [String: [Company]] = // however you want to read the data in here
func numberOfSections(in tableView: UITableView) -> Int {
dataSource.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
datasource[section].count
}
To create the datasource you'd need to go through all your companies and only fill the letters (section) of the ones actually existing.
Edit: I'd highly recommend you using AutoLayout to create your views

Dynamic section header height on runtime

I have UITableView in view controller with a section header and in the header, I have one UITextView with scroll disabled and pinned all UITextView edges to its super view.
Here is the code for automatic height change
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
guard let view = tableView.dequeueReusableHeaderFooterView(withIdentifier: "CreatePostHeaderView") as? CreatePostHeaderView else {
return nil
}
return view
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return UITableView.automaticDimension
}
And also set the estimated Height with this code
tableView.estimatedSectionHeaderHeight = 75
But on runtime when the text of UITextView exceeds height of 75, it doesn't change after that regardless of UITextView content. So, Do I need to add anything to make sure the table section header height changed according to UITextView content? Am I missing anything here?
When performing some action that changes the height of a cell (including header / footer cells), you have to inform the table view that the height has changed.
This is commonly done with either:
tableView.beginUpdates()
tableView.endUpdates()
or:
tableView.performBatchUpdates(_:completion:)
In this case, you want to call this when the text in your text view changes - easily done with a "callback" closure.
Here is an example of using a UITextView in a reusable UITableViewHeaderFooterView.
This will apply to loading a complex view from a XIB, but since this view is simple (only contains a UITextView), we'll do it all from code. This example uses 3 sections, each with 12 rows (default table view cells).
First, the table view controller class - no #IBOutlet or #IBAction connections, so just create a new UITableViewController and set its custom class to MyTestSectionHeaderTableViewController:
class MyTestSectionHeaderTableViewController: UITableViewController {
var myHeaderData: [String] = [
"Section 0",
"Section 1",
"Section 2",
]
override func viewDidLoad() {
super.viewDidLoad()
tableView.rowHeight = 50
tableView.keyboardDismissMode = .onDrag
tableView.sectionHeaderHeight = UITableView.automaticDimension
tableView.estimatedSectionHeaderHeight = 75
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "defCell")
tableView.register(MySectionHeaderView.self, forHeaderFooterViewReuseIdentifier: MySectionHeaderView.reuseIdentifier)
}
override func numberOfSections(in tableView: UITableView) -> Int {
return myHeaderData.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 12
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let c = tableView.dequeueReusableCell(withIdentifier: "defCell", for: indexPath)
c.textLabel?.text = "\(indexPath)"
return c
}
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let v = tableView.dequeueReusableHeaderFooterView(withIdentifier: MySectionHeaderView.reuseIdentifier) as! MySectionHeaderView
v.myTextView.text = myHeaderData[section]
v.textChangedCallback = { txt in
self.myHeaderData[section] = txt
tableView.performBatchUpdates(nil, completion: nil)
}
return v
}
}
and this is the UITableViewHeaderFooterView class. Note that it needs to conform to UITextViewDelegate so we can tell the controller the text has changed (so it can update the height when needed), and we pass back the newly edited text to update our data source:
class MySectionHeaderView: UITableViewHeaderFooterView, UITextViewDelegate {
static let reuseIdentifier: String = String(describing: self)
var myTextView: UITextView = {
let v = UITextView()
v.isScrollEnabled = false
return v
}()
var textChangedCallback: ((String) -> ())?
override init(reuseIdentifier: String?) {
super.init(reuseIdentifier: reuseIdentifier)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() -> Void {
contentView.addSubview(myTextView)
myTextView.translatesAutoresizingMaskIntoConstraints = false
let g = contentView.layoutMarginsGuide
NSLayoutConstraint.activate([
myTextView.topAnchor.constraint(equalTo: g.topAnchor),
myTextView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
myTextView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
myTextView.bottomAnchor.constraint(equalTo: g.bottomAnchor)
])
myTextView.delegate = self
}
func textViewDidChange(_ textView: UITextView) {
guard let str = textView.text else {
return
}
textChangedCallback?(str)
}
}
The result:

TableViewCell view disappear

I have UITextField at my UItableviewCell , when I click done button on keyboard , my Cell views is removed from Superview
override func layoutSubviews() {
super.layoutSubviews()
self.selectionStyle = .none
line.backgroundColor = .custom_gray()
line.snp.makeConstraints { (cons) in
cons.bottom.left.right.equalTo(phone).inset(0)
cons.height.equalTo(0.5)
}
stack.snp.makeConstraints { (cons) in
cons.left.right.equalTo(self).inset(25)
cons.centerY.equalTo(self)
cons.height.equalTo(230)
cons.bottom.equalTo(self).inset(15)
}
stack.dropShadow()
stack.layoutIfNeeded()
}
I solve this problem By adding my View Into TableviewCell content view
self.contentView.addSubview(stack)
phone.delegate = self
password.delegate = self
stack.snp.makeConstraints { (cons) in
cons.left.right.equalTo(self.contentView).inset(25)
cons.centerY.equalTo(self.contentView)
cons.height.equalTo(230)
cons.bottom.equalTo(self.contentView).inset(15)
}
self.contentView.backgroundColor = .white
and I return this view from tableviewHeaderContent
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let head = tableView.dequeueReusableCell(withIdentifier: headerid) as! ProfileLoginTableViewCell
return head.contentView
}

setup custom UITableViewHeaderFooterView for reusability

I have a custom section header view defined and registered like this:
class MySectionHeaderView : UITableViewHeaderFooterView {
var section : Int?
var button : UIButton?
}
class MyTableViewController : UITableViewController {
override func loadView() {
super.loadView()
self.tableView.register(MySectionHeaderView.self,
forHeaderFooterViewReuseIdentifier: "reuseIdentifier")
}
override func tableView(_ tableView: UITableView,
viewForHeaderInSection section: Int) -> UIView? {
let header = tableView.dequeueReusableHeaderFooterView(
withIdentifier: "reuseIdentifier")! as! MySectionHeaderView
header.textLabel?.text = titleForHeader(section: section)
header.section = section
if header.button == nil {
let button = UIButton(type: .system)
// ... configure button ... //
header.button = button
}
return header
}
}
This works. However it is very strange to put the button and other intializers inside the function tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView. as it breaks the separation of concerns principile. This functions should be about only to set the labels, etc.
Is there a way to initialize the header view, creating sub elements somewhere inside the class MySectionHeaderView?
Set only the data source dependent information of your header in viewForHeaderInSection. Move all the setup code inside the custom header class.
class MySectionHeaderView: UITableViewHeaderFooterView {
var section: Int?
lazy var button: UIButton = {
let button = UIButton(type: .system)
// ... configure button ... //
return button
}()
override init(reuseIdentifier: String?) {
super.init(reuseIdentifier: reuseIdentifier)
//Add subviews and set up constraints
}
}
Now in your delegate method,
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let header = tableView.dequeueReusableHeaderFooterView(
withIdentifier: "reuseIdentifier")! as! MySectionHeaderView
header.textLabel?.text = titleForHeader(section: section)
header.section = section
return header
}

UITableView delegate method not being called in swift 3

I have a table view that is subclassed and extended, then being set up in the View controller. The problem that I'm having is the delegate method ViewForHeaderInSection isn't being called, while the normal data source methods are being called.
(this is the TableView setup method, is called in ViewDidLoad. The table view is connected to the View Controller with IBOutlet)
func setup() {
self.dataSource = self as UITableViewDataSource
self.delegate = self
let nib = UINib(nibName: "MyTableViewCell", bundle: nil)
self.register(nib, forCellReuseIdentifier: "MyCell")
}
These are the extensions
extension MyTableView: UITableViewDataSource,UITableViewDelegate {
func numberOfSections(in tableView: UITableView) -> Int {
//print(Genres.total.rawValue)
return Genres.total.rawValue
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let tableSection = Genres(rawValue: section), let articleData = articleDictionary[tableSection] {
// print(articleData.count)
return articleData.count
}
print(0)
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell") as! MyTableViewCell
if let tableSection = Genres(rawValue: indexPath.section), let article = articleDictionary[tableSection]?[indexPath.row]{
cell.cellTitle.text = article.articleTitle
cell.cellImageView.image = article.articleImage
cell.cellEmojiReaction.text = article.articleEmojis
}
return cell
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let view = UIView(frame: CGRect(x: 0, y: 0, width: tableView.bounds.width, height: 40.0))
view.backgroundColor = .brown
let label = UILabel(frame: CGRect(x: 16, y: 0, width: tableView.bounds.width, height: 40))
label.textColor = .blue
let tableSection = Genres(rawValue: section)
switch tableSection {
case .breakingNews?:
label.text = "Breaking News"
case .scienceAndTech?:
label.text = "Science and Tech"
case .entertainment?:
label.text = "Entertainment"
case .sports?:
label.text = "Sports"
default:
label.text = "Invalid"
}
print("Function Run")
view.addSubview(label)
print(label.text ?? "Nothing Here")
return view
}
}
Here is the View controller Code:
class ViewController: UIViewController {
#IBOutlet weak var myTableView: MyTableView!
override func viewDidLoad() {
super.viewDidLoad()
myTableView.setup()
}
}
Is there any specific reason why the delegate method isn't being called? Thank you in advance for your time.
For this you have to also implement one more method heightForHeaderInSection along with viewForHeaderInSection method.
If you are using viewForHeaderInSection delegate method then it is compulsory to use heightForHeaderInSection method other wise section header mehtod is not called
Prior to iOS 5.0, table views would automatically resize the heights
of headers to 0 for sections where
tableView(_:viewForHeaderInSection:) returned a nil view. In iOS 5.0
and later, you must return the actual height for each section header
in this method.
Official Link for more description https://developer.apple.com/documentation/uikit/uitableviewdelegate/1614855-tableview
Add in viewDidLoad:
tableView.estimatedSectionHeaderHeight = 80

Resources