I want to do some kind of action according to the TableView Section Header Index Path.
Is there any method to trace the section header Index Path?
Short Answer
There is no existing method for that.
Solution
Declare a section number variable in your custom header class.
class CustomHeader: UITableViewHeaderFooterView
{
//
// MARK :- PROPERTIES
//
var sectionNumber: Int!
//
// MARK :- METHODS
//
override func awakeFromNib()
{
super.awakeFromNib()
// Initialization code ...
}
func configureWith(_ section: Int)
{
self.sectionNumber = section
}
} // end of class
Then in your view controller inside viewForHeaderInSection, assign section value while instantiating your custom header.
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView?
{
let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: "selectHeaderIdentifier") as! CustomHeader
header.configureWith(section)
return header
}
Now, you have in your custom header a section number property, use it as you want :]
header.sectionNumber
how about func headerViewForSection ?
override func tableView(tableView: UITableView, headerViewForSection section: Int) -> UITableViewHeaderFooterView {
let headerCell = tableView.dequeueReusableCellWithIdentifier("HeaderCell")
// do some stuf, e.g.
switch (section) {
case 0:
headerCell.backgroundColor = UIColor.cyanColor()
default:
headerCell.backgroundColor = UIColor.redColor()
}
return headerCell
}
Related
Here's my DisruptionsViewController which has the tableView. the function setUp() is used in another ViewController class to set up the DisruptionsViewController.
public class DisruptionsInfoViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
public override func viewDidLoad() {
super.viewDidLoad()
self.setUp()
tableView.dataSource = self
tableView.delegate = self
tableView.register(UINib(nibName: "DisruptionInfoTableViewCell", bundle: nil), forCellReuseIdentifier: "disruptionsInfoTableViewCell")
}
private func loadFromNib() -> UIView? {
let nibName = String(describing: DisruptionsInfoViewController.self)
let nib = UINib(nibName: nibName, bundle: Bundle(for: type(of: self)))
return nib.instantiate(withOwner: self, options: nil).first as? UIView
}
public func setUp() {
guard let disruptionsInfoViewController = self.loadFromNib() else { return }
disruptionsInfoViewController.frame = self.view.bounds
}
}
extension DisruptionsInfoViewController: UITableViewDataSource, UITableViewDelegate {
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "disruptionsInfoTableViewCell", for: indexPath) as? DisruptonInfoTableViewCell else { return UITableViewCell() }
return cell
}
}
Here's the tableViewCell class.
import UIKit
class DisruptonInfoTableViewCell: UITableViewCell {
#IBOutlet weak var testLabel: UILabel!
}
I can see the tableView in the view debugger, but unable to see it in the view as the tableViewCell is not registered for some reason.
Here's how I am using it in another controller's delegate method
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
guard let viewModel = presenter.headerViewModel(for: section) else { return nil }
let dateSummaryView = DateSummaryView(frame: .zero)
dateSummaryView.setup(with: viewModel)
let disruptionsViewController = DisruptionsInfoViewController()
return disruptionsViewController.view
}
Does anyone know where the problem could be?
I tried following tutorials from YouTube and other articles, they use the same approach but for some reason it doesn't work for me.
First, as mentioned in the comments, nothing in your setUp() is doing anything, so it can be removed.
The reason you are not seeing your table view rows in your other table's section header views is because here (I'll ignore the first three lines since they have nothing to do with this):
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
//guard let viewModel = presenter.headerViewModel(for: section) else { return nil }
//let dateSummaryView = DateSummaryView(frame: .zero)
//dateSummaryView.setup(with: viewModel)
let disruptionsViewController = DisruptionsInfoViewController()
return disruptionsViewController.view
// as soon as we return, disruptionsViewController is released
// and no code it contains will be executed
}
You created an instance of DisruptionsInfoViewController, pulled out its view, and then tossed away the controller code.
If you want to use this approach (rather odd, but we have to assume you have a logical reason to do this), you need to keep a reference to DisruptionsInfoViewController so its code can be used:
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let disruptionsViewController = DisruptionsInfoViewController()
// add disruptionsViewController as a child view controller
// this will "hold on to it" so its code can execute
addChild(disruptionsViewController)
disruptionsViewController.didMove(toParent: self)
return disruptionsViewController.view
}
Now, this is technically incorrect, as Apple's docs state:
Call the addChildViewController: method of your container view controller.
Add the child’s root view to your container’s view hierarchy.
Add any constraints for managing the size and position of the child’s root view.
Call the didMoveToParentViewController: method of the child view controller.
But, because we are returning the view for use as a section header view, we cannot perform 2. before calling didMove(toParent: self).
You didn't include in your post (or mention in your comments) how you're setting the Height of the section header, so, using:
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 150.0
}
We get this with your original code - section header view background color is blue, the "table view in header view" background color is green:
and we get this when using addChild():
Here is a complete example project: https://github.com/DonMag/Disrupt
While this will work, if you really want to embed a table view in another table view's section header(s), there are better ways to do it.
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 a requirement. Where I have to get the list of students and then I have to show their subjects in which they are enrolled in.
Example
Now you can see below I have list of students i.e Student1, student2, and so on. and each student have different number of subjects
What I have done So far:
I have created a Custom cell that Contains a Label and Empty vertical stackview.
Then in method tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) I am running the for loop that makes some UiLabel dynamically and adds them into the vertical stackview
Problem: By doing this I am getting what I want. But when I scroll up and down the for loop repeats data in the cell again and again on each scroll up/down
Please help if there is anyother way of doing that.
You can use tableview with section.
Set student name in section
Set your subjects in cell
This is sample of tableview with section.
https://blog.apoorvmote.com/uitableview-with-multiple-sections-ios-swift/
Here is the sample code it is just for your reference.
class TableViewController: UITableViewController {
let section = ["pizza", "deep dish pizza", "calzone"]
let items = [["Margarita", "BBQ Chicken", "Pepperoni"], ["sausage", "meat lovers", "veggie lovers"], ["sausage", "chicken pesto", "prawns", "mushrooms"]]
// MARK: - Table view data source
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return self.section\[section\]
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return self.section.count
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return self.items\[section\].count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("tableCell", forIndexPath: indexPath)
// Configure the cell...
cell.textLabel?.text = self.items[indexPath.section][indexPath.row]
return cell
}
Update
Customised section view
Create your custom view and show your view as section
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let view = UIView(frame: CGRect(x:0, y:0, width:tableView.frame.size.width, height:18))
let label = UILabel(frame: CGRect(x:10, y:5, width:tableView.frame.size.width, height:18))
label.font = UIFont.systemFont(ofSize: 14)
label.text = "This is a test";
view.addSubview(label);
view.backgroundColor = UIColor.gray;
return view
}
Sample code for Customised section
Update 2
Custom header with reference of cell
override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerCell = tableView.dequeueReusableCellWithIdentifier("HeaderCell") as! CustomHeaderCell
headerCell.backgroundColor = UIColor.cyanColor()
switch (section) {
case 0:
headerCell.headerLabel.text = "Student Name 1";
//return sectionHeaderView
case 1:
headerCell.headerLabel.text = "Student Name 2";
//return sectionHeaderView
case 2:
headerCell.headerLabel.text = "Student Name 3";
//return sectionHeaderView
default:
headerCell.headerLabel.text = "Other";
}
return headerCell
}
You can try a different approach. Make a list with number of sections = number of students. Each section should have number of rows equal to the subjects for that student. This can be achieved easily by making a student model with subjects array as it's property.
class Student: NSObject {
var subjectsArray : [String] = []
}
First add this extension to your source code.
extension UIStackView{
func removeAllArrangedSubviews() {
let removedSubviews = arrangedSubviews.reduce([]) { (allSubviews, subview) -> [UIView] in
removeArrangedSubview(subview)
return allSubviews + [subview]
}
removedSubviews.forEach({ $0.removeFromSuperview() })
}
}
Now override this method in your Custom cell class. it will remove all child views from stackview before reuse.
override func prepareForReuse() {
super.prepareForReuse()
self.stackView.removeAllArrangedSubviews()
}
I am using a custom UITableViewHeaderFooterView for me TableView. I was trying to implement hiding and showing rows in a section(which I have working). I decided to add a button (>) to the section header so that I can rotate it when the section is "expanded/collapsed".
The problem I have appears when I click the button. When the rotateCollapseButton() function is called, the (>) buttons in all the section headers rotate, not just the one that was clicked. Sometimes it'll even exclude the button that was clicked or clicking one will affect a different one and not itself.
How can I make it so that only the correct button will rotate?
This is the code I have for the custom Header I created.
var rotated:Bool = false
var section:Int?
weak var delegate:MessageGroupHeaderDelegate?
#IBAction func expandCollapseButtonClicked(_ sender: Any) {
rotateCollapseButton(sender as! UIButton)
delegate?.didPressExpandCollapseButton(atSection : self.section!)
}
func rotateCollapseButton(_ button:UIButton) {
UIView.animate(withDuration: 0.5) { () -> Void in
var rotationAngle:CGFloat = CGFloat(M_PI_2)
if self.rotated {
rotationAngle = CGFloat(0)
}
button.transform = CGAffineTransform(rotationAngle : rotationAngle)
self.rotated = !self.rotated
}
}
EDIT: Code where the header is initialized...
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
// Dequeue with the reuse identifier
let cell = self.massMessageGroupsTableView.dequeueReusableHeaderFooterView(withIdentifier: "MessageGroupTableViewHeader")
let header = cell as! MessageGroupTableViewHeader
header.groupNameLabel.text = messageGroupsMap[section]?.messageGroup.name
header.section = section
header.setComposeButtonImage()
header.delegate = self
return cell
}
Thank you!
In your header setting, trying doing this instead:
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
// Dequeue with the reuse identifier
let cell = self.massMessageGroupsTableView.dequeueReusableCell(withIdentifier: "MessageGroupTableViewHeader")
let header = cell as! MessageGroupTableViewHeader
header.groupNameLabel.text = messageGroupsMap[section]?.messageGroup.name
header.section = section
header.setComposeButtonImage()
header.delegate = self
let containingView : UIView = UIView()
containingView.addSubview(header)
return containingView
}
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
}