swift: only show section headers if more than 1 section - ios

i have a table with a variable number of sections. If there are more than one section i want to show show custom Headers, but if there is only one, i don't want a header at all.
My Approach is to use func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? and return nil if i have only one section. But instead of showing no header, i get an empty header. Any ideas?
here is my code
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Orders"
tableView.dataSource = self
tableView.delegate = self
let headerNib = UINib.init(nibName: "SectionHeader", bundle: Bundle.main)
tableView.register(headerNib, forHeaderFooterViewReuseIdentifier: "SectionHeader")
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
if(ordersList.count < 2) {return nil}
let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "SectionHeader") as! SectionHeader
headerView.label.text = "\(sectionHeaders[section]) (\(ordersList[section].count))"
headerView.button.setTitle(self.collapsed[section] ? "▶" : "▼", for: .normal)
headerView.onTap = {
self.collapsed[section] = !self.collapsed[section]
headerView.button.rotate(self.collapsed[section] ? CGFloat(-.pi/2.0) : CGFloat(.pi/2.0), duration: 0.1, completion: {
self.tableView.beginUpdates()
self.tableView.reloadSections([section], with: .fade)
self.tableView.endUpdates()
})
}
return headerView
}

use heightForHeaderInSection delegate method also. Make height 0 if condition not satisfy. For Reference -
public func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
if(ordersList.count < 2) {return 0.1}
else { return HEIGHT} //custom value of view height
}

You have to use the following methods to achieve the desired result:
func numberOfSections(in tableView: UITableView) -> Int {
if(ordersList.count < 2) {return nil}
return ordersList.count
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
if(ordersList.count < 2) {return nil}
//else return some view }
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
if(ordersList.count < 2) {return 0}
return some_height
}

Use tableView.numberOfSections to get the section count. And use this value in tableView(_:viewForHeaderInSection:) to check whether you want return SectionHeader or nil. You need to return nil in case you don't want to show header in a particular section.
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
guard tableView.numberOfSections > 1 else {
return nil
}
let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "SectionHeader") as! SectionHeader
//add rest of the code...
return headerView
}
Implement tableView(_:heightForHeaderInSection:) to return the header height based on the section. Return 0 if you don't want to show header in a section otherwise it will take the default header height.
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return (tableView.numberOfSections > 1) ? 100.0 : 0.0
}

you should override numberOfSections method of tableview. And you should not use dequeueReusableHeaderFooterView for section header view. Section header views should be a custom view.
-- numberOfSections
func numberOfSections(in tableView: UITableView) -> Int {
return sectionCount
}
-- sectionHeaderView
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
// you can store data on a view model
let cellViewModel = viewModel.returnViewModel(index: section)
switch cellViewModel.type {
case .sampleViewModel1:
return UIView() // sampleView1
case .sampleViewModel2:
return UIView() // sampleView2
}
}

Related

Error after cell insertion: invlalid number of sections

After receiving a new object, I call this func to insert a cell:
private func addCellToTheTop(recipe: Recipe) {
guard let recipeTableView = self.recipeTableView else { return }
recipesForShow.insert(recipe, at: 0)
recipeTableView.beginUpdates()
recipeTableView.insertRows(at: [IndexPath.init(row: 0, section: 0)], with: .automatic)
recipeTableView.endUpdates()
}
But I get an error
Why the number of section does not match?
If it is important:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func numberOfSections(in tableView: UITableView) -> Int {
return recipesForShow.count
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return TableCellConfig.spaceBetweenCells
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = UIView()
headerView.backgroundColor = UIColor.clear
return headerView
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//...
}
You are getting this exception because you are appending a new element to the array that is being used to determine the numberOfSection whereas you are inserting a new row into the tableView. To fix this you need to insert section instead of inserting new row, here's how:
recipeTableView.insertSections([0], with: .automatic)

How to use a fixed cell in table and then a list of item below that?

I am using below code for one fixed cell at the top and then list of items in below in a single tableview, I am able to load the data from firestore database, but the first item in from the database is not shown, it is kind of hidden behind the fixed "detailscell", rest the fixed cell and all other items from the database shows up, how to fix that?
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// print(posts)
return posts.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 && details1.count > 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "detailsCell") as! DetailsCellInHomeScreen
cell.set(details: details1[indexPath.row])
return cell
} else if posts.count > (indexPath.row - 1) {
let cell = tableView.dequeueReusableCell(withIdentifier: "postCell", for: indexPath) as! PostTableViewCell
cell.set(post: posts[indexPath.row - 1])
return cell
} else {
return UITableViewCell()
}
}
TableView has a function like below:
// MARK: - Headers and Footers
extension MyTableViewController {
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
return MyCustomView()
}
}
You can use this function to return any view you want as a section header. Also note that sectionHeader views are sticky in default style tableView (not grouped style!). So you can use this as a advantage.
There are many tutorials like this one on the internet to follow and learn how to build a custom section section header for UITableView
You should add header on the table view:
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// print(posts)
return posts.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "postCell", for: indexPath) as! PostTableViewCell
cell.set(post: posts[indexPath.row - 1])
return cell
}
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = UIView.init(frame: CGRect.init(x: 0, y: 0, width: tableView.frame.width, height: 50))
let label = UILabel()
label.frame = CGRect.init(x: 5, y: 5, width: headerView.frame.width-10, height: headerView.frame.height-10)
label.text = "A Label on detailsCell"
headerView.addSubview(label)
return headerView
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 50
}
if the tableView has several section you can realize the section with section: Int object
tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? uses for create the header view and tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat uses for set header height

How to make final footer in UITableView?

I tried to make a Footer in UITableView like in iOS app "Contacts".
I have simple tableView with simple array:
items = ["aa", "bb", "cc", "dd"]
I want to make empty space under last cell.
I tried to add this cellForRowAt:
if indexPath.row == self.converterItems.count-1 {
cell.separatorInset = UIEdgeInsetsMake(0, cell.bounds.size.width, 0, 0)}
But it delete separator only for one cell.
override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
var header :UITableViewHeaderFooterView = UITableViewHeaderFooterView()
header.contentView.backgroundColor = UIColor(red: 255.0/255.0, green: 255.0/255.0, blue: 255.0/255.0, alpha: 1)
return header
}
This method looks good, but it lost separator below last cell...
You should create a UIView and assign it to your table's footer view
myTableView.tableFooterView = createTableFooterView()
func createTableFooterView() -> UIView {
let footerView = UIView()
...
...
...
return footerView
}
To make the table endless. You'd better set UITableViewStyle.grouped
let tableView = UITableView(frame: CGRect.zero, style: .grouped)
And Then:
extension ViewController: UITableViewDelegate, UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return [].count// your array
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "", for: indexPath)
// your cell
// yourIdentifier, and you should register cell Identifier for tableView at first
return cell
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return CGFloat.leastNormalMagnitude
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return ""
}
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 50
}
func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
return "83 KOH"
}
}

Another solution instead of tableView.reloadData()

I am calling batch updates(begin/end updates) on a tableView using this code:
let oldImageSet = Set(oldImageArray)
let newImageSet = Set(self.images)
let missingImages = newImageSet.subtracting(oldImageSet)
let missingImageIndexPaths = Array(missingImages).map{NSIndexPath(row: self.images.index(of: $0)!, section: 0)}
//Array(missingImages).map{NSIndexPath(forRow:newImageUUIDArray.indexOf($0)!,inSection:1)}
let removedImages = oldImageSet.subtracting(newImageSet)
let removedImageIndexPaths = Array(removedImages).map{NSIndexPath(row:oldImageArray.index(of: $0)!,section:0)}
self.tableView.beginUpdates()
if missingImageIndexPaths.isEmpty == false {
self.tableView.insertRows(at: missingImageIndexPaths as [IndexPath],with:.top)
}
if removedImageIndexPaths.isEmpty == false {
self.tableView.deleteRows(at: removedImageIndexPaths as [IndexPath],with:.none)
}
self.tableView.endUpdates()
Which is working alright.
Edit
This is my tableView section counts and how I have it set up:
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 55
}
func numberOfSections(in tableView: UITableView) -> Int {
return posts.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
But now I have another tableView I would like to be able to call this on, But it has 'Sections' so When I use this I get a crash...?
Does anyone know how to use this code on a tableView with sections?
Many thanks in advance!
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 55
}
func numberOfSections(in tableView: UITableView) -> Int {
return posts.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
replace code with following code :
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 55
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return posts.count
}
You should just apply what you're doing with rows to sections which means changing IndexPaths to IndexSets. IndexSet can be initialized with an array of Ints where each Int represents a corresponding section.
let oldImageSet = Set(oldImageArray)
let newImageSet = Set(self.images)
let addedImages = newImageSet.subtracting(oldImageSet)
// map each image to the section equivalent to its index in `self.images`
let addedImageSections = Array(missingImages).map{ self.images.indexOf($0)! }
let addedImageIndexSet = IndexSet(addedImageSections)
// repeat above process but in reverse to find images that have been removed
let removedImages = oldImageSet.subtracting(newImageSet)
let removedImageSections = Array(removedImages).map{ oldImageArray.index(of: $0)! }
let removedImageIndexSet = IndexSet(removedImageSections)
self.tableView.beginUpdates()
// if images have been added insert new sections
if !addedImageIndexSet.isEmpty {
self.tableView.insertSections(addedImageIndexSet, with: .top)
}
// if images have been deleted remove sections
if !removedImageIndexSet.isEmpty {
self.tableView.deleteSections(removedImageIndexSet, with: .none)
}
self.tableView.endUpdates()

Swift - header for section does not show

This is my code
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 61.0
}
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerCell = tableView.dequeueReusableCellWithIdentifier("CustomHeaderCell") as! CustomHeaderCell
headerCell.titleLabel.text = "user data"
return headerCell
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 2
}
Unfortunately, custom header view does not show up.
However, when I use just this:
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return "user data"
}
section headers are present, what is wrong with my custom header view code?
My bad, I blindly put these functions into dataSource although they belong to tableViewDelegate
to make it work:
extension UserProfileDelegate: UITableViewDelegate {
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerCell = tableView.dequeueReusableCellWithIdentifier("CustomHeaderCell") as! CustomHeaderCell
headerCell.titleLabel.text = "user data"
return headerCell
}
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 61.0
}
}

Resources