Why headerView is always nil in numberOfRowsInSection? - ios

I have to make a dynamic datasource for a table, that satisfies the criteria:
we do not know exact number of sections
we do not know exact title for header in section
we do not now how many rows it will be in each section each time
To detect number of rows per section I have the following code:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let header = tableView.headerView(forSection: section)
return self.servicesData?.filter({ $0.clubName == header?.textLabel?.text }).count ?? 0
}
where servicesData is the object array that is to be filtered by the header title aka clubName. Titles are gathered from the object array.
For some reason I always get nil when try to access the header. So far I tried calling tableView(_:titleForHeaderInSection:) directly in numberOfRowsInSection but obviously with no luck.
Feedback much appreciated.

It is nil because it is not created at that moment. You should not use tableView.headerView(forSection: section) in numberOfRows. You should use the model from where you get the title to use it to filter in the array.
You should fetch all of your data before populating the table view and first. Then you should implement:
func numberOfSections(in tableView: UITableView) -> Int {
return headerModels.count
}
where headerModels it's an array of objects with your title and it's items like:
class HeaderModel {
let title: String
let items: [YourItem]
}
and then:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return headerModels[section].items.count
}

So, as per #shurtugal 's solution it is possible to pass-in an array of objects to populate the table. I thought that creating a utility class for this very case may be too much. So here's the slightly altered way:
Consider a dictionary var servicesData = [String: [CustomObject]]() that is populated each time the ViewController shows up. The dictionary can have indefinite number of keys and indefinite number of values inside each key. Algorithm of populating the dictionary is taken from this answer.
Thereby, UITableViewDatasource methods can be defined like this:
func numberOfSections(in tableView: UITableView) -> Int {
self.servicesData.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return Array(self.servicesData.values)[section].count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
/* cell creation code code */
/// sample line
cell?.someLabel.text = Array(self.servicesData.values)[indexPath.section][indexPath.row]
return cell
}

Related

Displaying Different Row in Different Section in UITableView

I've fetched This Data from API - Which is next 7 days weather report. I want to display this as tableview where
Date will be the as Section
The temperature (Min / Max) as row
But If I select the section number like this -
func numberOfSections(in tableView: UITableView) -> Int {
return myList.count // myList is an array of containing the 7 day's data
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
It printing the same row (1st data of the array) in all section. So how can I print separate weather at separate section without splitting my array?
When it comes to section and rows in UITableView, the key is usually 2D arrays.
Try to create a 2D array like this:
myList[sectionItems][rowItems]
Inside this array should look something like this:
myList = [
[date, minTemp, maxtemp],
[date, minTemp, maxtemp],
[date, minTemp, maxtemp],
[date, minTemp, maxtemp]
]
So your UITableViewDelegate and DataSource functions will look like this:
func numberOfSections(in tableView: UITableView) -> Int {
return myList.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myList[section].count
}
Above example will allow you to have 4 sections and each section will have 3 rows.
Then you can print the date on first row and the rest on other rows.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
cell.textLabel.text = myList[indexPath.section][indexPath.row]
return cell
}

Can you update the number of sections in a UITableView after CellForRowAt?

I'm new to Swift so please bear with me. I've got a program which uses a JSOn file which is decoded into the following
struct Artwork: Decodable {
let id: String
let title: String
let artist: String
var location: String }
after I call my cellForRowAt method a unique location is stored in a locationArray (when printed this is correct) , and the table reflects this when run, however when I try and use numberOfSections method and return locationArray.count no data is shown in the table and no sections are created, if I run a print line in numberOfSections the locationArray is empty. Is there a way to call numberOfSections after cellForRowAt? or am I approaching the problem wrong form the outset?
when you hook up a tableview delegate + datasource you need 2 specific delegates in order for it to run.
If you want to update a particular row with say new data you would have to do these steps:
grab arraymodel data
var dataFromArray = self.exampleArray[Int]
add any data that you need or need to replace
var dataFromArray.name = "joe smith"
reload the specific row where is being represented by said model from array
tableView.reloadRows(at: [IndexPath(row: Int, section: Int)], with: .none)
numberOfRowsInSection
-If you have 5 models in your array, the 'cellforrowat' method will get called 5 times
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.exampleArray.count
}
CellForRowAt
-for each cell you need to be created here is where you can set up the data dynamically based off the 'indexpath'
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let dataFromArray = self.exampleArray[indexPath.row]
return UITableViewCell()
}

swift uitableview repeats first cell

Im a complete beginner to swift and iOS and I'm trying to write some code which will take some json and put it into an array of objects and then use that array to populate the tableview.
The problem is when I try to populate the tableview it just prints the first one so it looks like this:
here is the code
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "comicCell", for: indexPath) as! ComicCell
cell.title.text = comics?[indexPath.row].title
cell.dateOnSale.text = comics?[indexPath.row].dateOnSale
return cell
}
I tried to print the array of objects in a simple for loop and it works fine which led me to believe my problem is in the function above
The cellForRowAt method seems OK, I think you're having trouble with the numberOfSections and numberOfRowsInSection methods of tableView, the former must return 1 and the latter must return comics.count in your case.
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return comics.count
}

swift - create a page for both repeated data and for no data

I need an advice with ViewContoller's scheme. I should create a view with Billing addresses. There can be no address at all, or some addresses. If there no one, there should be only a button "Add New". And if there are addresses, each should have buttons Edit, Remove, and "Add New" too.
I have data for this VC as JSON, parsed and saved to plist.
So what is logic to make this View looks different depends on 1) if there are addresses or not? and 2) if there 1, or 2, or maybe 20 billing addresses?
Thanks a lot!
I solved issues like this with UITableVIew and the UITableViewDataSource and UITableViewDelegate:
setup the table view for one section (adresses)
func numberOfSections(in tableView: UITableView) -> Int {return 1;}
return the address arrays length in the delegate method
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
return adresses.count
}
set the view for footer if the arrays length is 0
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
if adresses.count == 0 {
let vw = YourViewClass()
//I use blockskit library here (vw.bk_) to recognize a tap, but you can add a button by yourself
vw.bk_(whenTapped: {
//Create and present your next viewcontroller to
})
return vw
}
return nil
}
set the footers height to 0 if there are addresses
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
if addresses.count > 0 {
return YOUR_DESIRED_FOOTER_HEIGHT_FOR_INPUT
}
return 0
}
create row for each address
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let address = addresses[indexPath.row]
let tableViewCell = UITableViewCell() //maybe you have to create your own if the layout does not fit
//set tableViewCell's title / description to show address values
return tableViewCell
}
In this case, the footer view (you can do the same in the header if you want) with the add button is shown when no addresses are available, and it is hidden, when addresses are available.

How can I safely use an un-initialized array in a UITableView and show an empty table

How to prevent crashes when an array is empty and you make a request from a UITableView or UIPickerView?
My current method is to always initialize my arrays before using them with dummy data but I'm not really happy with this method since sometimes the dummy data is not needed and even-worse, sometimes it doesn't even make sense to show the data, in fact most of the time what I want is to show an empty table if there is no data.
For instance if I will be retrieving an array from NSUserDefaults to be used in a UITableView I usually initialize it in the AppDelegate as follow...
AppDelegate.swift:
NSUserDefaults.standardUserDefaults().registerDefaults([
keyMyAarray:["Dummy Data"]// initializing array
])
SomeViewController:
var myArray = read content from NSUserDefaults...
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
fun tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myArray.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = UITableViewCell()
cell.textLabel.text = myArray[indexPath.row]
return cell
}
Again, how can I safely use an un-initialized array in a UITableView and show an empty table?
There is no need to put "dummy data" in your array. You can just initilize an empty array. like below
var myArray = [String]()
And in numberOfRowsInSection return myArray.count. If count is zero, cellForRowAtIndexPath will not be called and you are safe to go.
3 empty rows by default.
var myArray:Array<String>? = ...
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
fun tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myArray?.count ?? 3
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = UITableViewCell()
if let arrayStrings = myArray, arrayStrings.count > indexPath.row {
cell.textLabel.text = arrayStrings[indexPath.row]
}
return cell
}

Resources