I am playing with a table as below (I've seen multiple threads on this topic but couldn't answer my question):
func numberOfSections(in _: UITableView) -> Int {
3
}
func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return 3
} else if section == 1 {
return 4
} else {
return 2
}
}
Then after clicking on a cell I want a new cell inserted into the same section at row index 0. I wrote the below:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.insertRows(at: [IndexPath(row: 0, section: indexPath.section)], with: .left)
}
I get this error:
Exception NSException * "Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (3) must be equal to the number of rows contained in that section before the update (3), plus or minus the number of rows inserted or deleted from that section (1 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out)."
I assume I should somehow update the data source, but since in my case I return const values for rows in sections is there a way to update the the number of rows in section? (the numberOfRows in section only returns immutable value). Thank you
in my case I return const values
There you go! If you are going to modify the number of rows you need variables
For example
var sections = [3,4,2]
func numberOfSections(in _: UITableView) -> Int {
return sections.count
}
func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int {
return sections[section]
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
sections[indexPath.section] += 1
tableView.insertRows(at: [IndexPath(row: 0, section: indexPath.section)], with: .left)
}
// Variable and ViewDidLoad
var auxRow = 0
var auxSection = 0
override func viewDidLoad() {
super.viewDidLoad()
}
//Number Of Sections, only one section
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
auxSection += 1
print("times section: ", auxSection)
return 1
}
//Number Of Rows
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
auxRow += 1
print("times row: ", auxRow)
return 2
}
// TableView Configure the cell
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let textCellIdentifier = "cellText"
let cell = tableView.dequeueReusableCellWithIdentifier(textCellIdentifier, forIndexPath: indexPath)
let variable : String = indexPath.row.description
cell.textLabel?.text = variable
return cell
}
Output:
times section: 1
times row: 1
times section: 2
times row: 2
times section: 3
times row: 3
times section: 4
times row: 4
The method numberOfRowsInSection is being called 4 times. Why is that happening?
Apple makes no guarantees how often UIKit may call one of your delegate methods. They own the framework. We can expect them to cache that value but nothing requires them to do that.
You can set a breakpoint inside your numberOfRowsInSection method to see what it is being used for internally. There is no reason why it shouldn't be called multiple times, as it is called any time the tableview internally needs to know how many rows it has. It is called 4 times because Apple's tableview code needs to know, at 4 different times, how many rows are in your one section.
I have a UITableView which has multiple sections and I want each section to have a different number of rows. What I have in code right now is
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//habitsByDay is a global array contain the number of rows for each section,
//with each index of the array corresponding to the each of the sections
return habitsByDay.removeAtIndex(0)
}
Will this method be called when each section is loaded (i.e. it should be called habitsByDay.count-many times).
The table view data source methods can be called at any time, in any order.
In particular, they should not have side effects as in your example.
If habitsByDay is an array with the number of rows for each section
then just do
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return habitsByDay[section]
}
You have to use if statements for that.
For example:
if (section == 0) { //First section
return 20; } //20 rows in first section
if (section == 1) { //Second section
return 10; } //10 rows in second section
Or in your case for example.
if (section == 0) {
return habitsByDay.count }
I have a static grouped table view that has 5 sections (all the sections have headers, no footers). I created all of this using a Storyboard. Now, how can I hide the first/top UITableViewSection (including the header). I tried making an outlet to the UITableViewSection but it tells me that it is not valid (undeclared type):
#IBOutlet var section: UITableViewSection!
I did it this way because I was planning on doing:
section.hidden = true
Can it not be done this way?
My delegates and data sources are set up 100% correctly.
Swift 5:
You can use the delegate method heightForHeaderInSection
:
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
if (section == 0) {
return 0.0
}
return UITableView.automaticDimension
}
Earlier than Swift 5: Use UITableViewAutomaticDimension instead of UITableView.automaticDimension
If it's not working with height 0.0, use height 0.1
If you want no cells in a particular section, use the delegate method:
func numberOfRowsInSection(section: Int) -> Int {
if (section == 0) {
return 0
}
else {
// return the number of rows you want
}
}
Or to a neater switch-case syntax:
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
switch section {
case 0:
return 0.0
default:
return UITableView.automaticDimension
}
}
I tested both and they are working fine.
I also wish you could just make an #IBOutlet to a section and hide it, but sadly it seems not, so...
Based on various suggestions here, I've established the following, which doesn't require any interfering with explicit size values, and preserves whatever you may have set on a storyboard/XIB already. It just makes the header nil and row count 0 for any section you want to hide (which results in a size of 0.0).
Obviously, you can configure sectionShouldBeHidden to work however you need; hiding #1 & #3 are just arbitrary examples.
Swift v5
private func sectionShouldBeHidden(_ section: Int) -> Bool {
switch section {
case 1, 3: return true
default: return false
}
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if sectionShouldBeHidden(section) {
return nil // Show nothing for the header of hidden sections
} else {
return super.tableView(tableView, titleForHeaderInSection: section) // Use the default header for other sections
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if sectionShouldBeHidden(section) {
return 0 // Don't show any rows for hidden sections
} else {
return super.tableView(tableView, numberOfRowsInSection: section) // Use the default number of rows for other sections
}
}
Update: Unfortunately, the above is only enough if the style of the table view is Plain. When it's Grouped, there's also additional space added between each section, which needs taking care of too.
This extra space is the section's footer, so can be handled like so:
override public func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
if sectionShouldBeHidden(section) {
return CGFloat.leastNormalMagnitude // Use the smallest possible value for hidden sections
} else {
return super.tableView(tableView, heightForFooterInSection: section) // Use the default footer height for other sections
}
}
I tried all the solutions here with no success. At the end, adding these delegate methods this one worked:
Swift 5:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return section == 4 ? 0 : return super.tableView(tableView, numberOfRowsInSection: section)
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return section == 4 ? 0.1 : super.tableView(tableView, heightForHeaderInSection: section)
}
override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return section == 4 ? 0.1 super.tableView(tableView, heightForFooterInSection: section)
}
Note that you need to return 0.1 in height, returning 0 won't do it.
0.0 did not work for me. I had to do this in order to make it work.
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
switch section {
case 0:
return 0.01
default:
return UITableViewAutomaticDimension
}
}
For group UITableView with static cells only this solution works:
self.tableView.sectionHeaderHeight = 0;
self.tableView.sectionFooterHeight = 0;
override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
let count = self.tableView(tableView, numberOfRowsInSection: section)
if count == 0 {
return CGFloat(Double.leastNormalMagnitude)
}
return 44.0
}
For anyone wanting to hide sections because they are using a static grouped tableView with a dynamic number of sections, the solution below may be of help. In my case, each section with data to display needed to have a custom header. Any section that did not have data, needed to be hidden fully.
The answer above was of great help in my scenario. However, for those who don't always know which section(s) will need to be hidden here is a solution for you extending on the above.
In my scenario, I have up to 12 entries in an array that I want to show in up to 12 sections (amongst other sections in a grouped tableView). If there are less than 12 entries to display, I want to hide the unnecessary sections by giving them 0 height and 0 rows. I also wanted to hide the headerView.
To do this, I did the following:
Set up your tableView as per the excellent answer #sasquatch gave
above.
In the numberOfRowsInSection(section: Int) and tableView(_
tableView: UITableView, heightForHeaderInSection section: Int) functions, check whether the rows/height should be 0.
In my case, I was using sections 1 - 12 for my dynamic data so I used code as below:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//For section 0 and 13, just 1 row is ok
if section == 0 || section == 13 {
return 1
}
//For sections 1 - 12, determine if we have data to populate it, or if we should hide it
if section <= dynamicDataToDisplay.count {
return 2
}
//If it's section 1 - 12, but we don't have a corresponding data entry in dynamicDataToDisplay, then just return 0 rows
return 0
}
The code for the heightForHeader function is similar in logic:
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
switch section {
case 0:
return 45.0
case 13:
return UITableViewAutomaticDimension
default:
if dynamicDataToDisplay.count >= section {
return 25.0
} else {
return 0.0
}
} //end switch
}
Even after setting up these functions, I found that I was still getting headers appearing for the sections I wanted to hide. I guess I thought that viewForHeaderInSection would not be called if the numberOfRows was 0 and heightOfHeader was also 0, but it was still being called.
Adding the following helped ensure that the header wasn't unnecessarily created:
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
//set up header for dynamic data sections
if 1 ... 12 ~= section {
if tableView.numberOfRows(inSection: section) == 0 {
return nil
}
//Continue with the header set up for valid sections with rows to display data
......
}
}
This solution might help anyone who is still getting a header being created despite its height and rows being set to 0.
I'm new to Swift and iOS programming and I'm having a play with making some simple apps. I am trying to build a master-detail app.
In the master view I've given the tableview two sections and I've set the content of the table view to "static cells". Initially I gave each section 3 rows and was able to successfully run the app with the following code in the mainviewcontroller file:
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 2
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
I am now wanting to have 11 rows in the first section, and 5 rows in the second section but the changes I have tried to the code prevent the app from running. I've tried:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 16
}
and I've tried:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 11
}
but it falls over. What am I doing wrong here?
You should use the section to decide how many rows there should be in that section. For example, you could have a variable in your view controller:
let numberOfRowsAtSection: [Int] = [11, 5]
Now in numberOfRowsForSection:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var rows: Int = 0
if section < numberOfRowsAtSection.count {
rows = numberOfRowsAtSection[section]
}
return rows
}