Sections in UITableView with Custom Cells - swift

I have the following code thus far.
var someData = [SomeData]()
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell1", for: indexPath) as! Cell1
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell2", for: indexPath) as? Cell2
let someData = [indexPath.row]
//Set up labels etc.
return cell!
}
}
I need Cell1 which is a static cell and will always remain at indexPath 0 to be in a section called "Section1" for example & all of the Cell2's to be in a section called "Section2"
Other DataSource & Delegate Methods;
func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return 1
} else {
return someData.count
}
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if section == 0 {
return "Section1" }
else {
return "Section2"
}
}
This returns me everything I need for the first section, however, when it comes to the second section (because of the code inside cellForRowAtIndex somewhere) section 2 contains Cell2 at indexPath 0.
Any help greatly appreciated.

Root cause:
In cellForRowAtIndexPath check for indexPath.section instead of indexPath.row
Fix:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell1", for: indexPath) as! Cell1
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell2", for: indexPath) as? Cell2
let someData = [indexPath.row]
//Set up labels etc.
return cell!
}
}

Related

"Index out of range" Error with two Tableviews in one Viewcontroller

I have two Tableviews in one ViewController. But if I run this my code I will get this error on line let post = posts[indexPath.row]:
"Index out of range"
I think it has be something with the posts.count, but I can't figure it out. When I ran this code without the second Tableview Tableview_moremaps everything works just fine.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if comments.count == 0 {
self.tableView.setEmptyMessage("No comments yet!")
} else {
self.tableView.restore()
}
if posts.count == 0 {
self.Tableview_moremaps.setEmptyMessage("No other maps yet!")
}else{
self.Tableview_moremaps.restore()
}
if tableView == tableView {
return comments.count
}else{
return posts.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if tableView == self.tableView {
let cell = tableView.dequeueReusableCell(withIdentifier: "Comment", for: indexPath) as? CommentTableViewCell
let comment = comments[indexPath.row]
cell?.comment = comment
cell?.delegate = self
return cell!
} else if tableView == Tableview_moremaps {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as? MoremapsTableViewCell
cell?.Map_image.image = nil
let post = posts[indexPath.row]
cell?.post = post
cell?.delegate = self
return cell!
}
return UITableViewCell()
}
In numberOfRowsInSection, update the if-condition
if tableView === self.tableView {
return comments.count
} else {
return posts.count
}
Currently tableView == tableView is always true and returned value is comments count.
I think the problem is in the way you're using cellForRowAt
Try giving each tableview an identifier with tableView.accessibilityIdentifier = "myIdentifier"
and then cellForRowAtwould be like following:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if tableView.accessibilityIdentifier == "myIdentifier" {
let cell = .....
...
...
...
...
...
you can try this
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if tableView == self.tableView {
return comments.count
}
else{
return posts.count
}
}
//Your this code is perfect.(no issue here)
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if tableView == self.tableView {
let cell = tableView.dequeueReusableCell(withIdentifier: "Comment", for: indexPath) as? CommentTableViewCell
let comment = comments[indexPath.row]
cell?.comment = comment
cell?.delegate = self
return cell!
} else if tableView == Tableview_moremaps {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as? MoremapsTableViewCell
cell?.Map_image.image = nil
let post = posts[indexPath.row]
cell?.post = post
cell?.delegate = self
return cell!
}
return UITableViewCell()
}

Use 2 Table View in the same View Controller (Swift 4)

I have a problem handling 2 tables on the same screen. Every time he keeps crashing. Can someone help me?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell: TableViewCellComunicazioni?
if tableView == self.tableViewNotifica {
cell = tableView.dequeueReusableCell(withIdentifier: "cellNotifica", for: indexPath) as? TableViewCellComunicazioni
let dataNotifica = structNotifica[indexPath.row].dateNotifica
let testoNotifica = structNotifica[indexPath.row].textNotifica
cell?.dateNotification.text = "\(date!)"
cell?.textNotification.text = "\(text!)"
return cell!
}
if tableView == self.tableViewInbox {
cell = tableView.dequeueReusableCell(withIdentifier: "cellInbox", for: indexPath) as? TableViewCellComunicazioni
let email = structInbox[indexPath.row].email
let messaggio = structInbox[indexPath.row].messaggio
let data = structInbox[indexPath.row].data
cell?.emailInbox.text = "\(email!)"
cell?.messaggioInbox.text = "\(message!)"
cell?.dataInbox.text = "\(date!)"
return cell!
}
return UITableViewCell()
}
This could be a probable fix for your problem:
Coding Example:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if tableView == self.tableViewNotifica {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellNotifica", for: indexPath) as? TableViewCellComunicazioni
let dataNotifica = structNotifica[indexPath.row].dateNotifica
let testoNotifica = structNotifica[indexPath.row].textNotifica
cell?.dateNotification.text = "\(date!)"
cell?.textNotification.text = "\(text!)"
return cell!
}
if tableView == self.tableViewInbox {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellInbox", for: indexPath) as? TableViewCellComunicazioni
let email = structInbox[indexPath.row].email
let messaggio = structInbox[indexPath.row].messaggio
let data = structInbox[indexPath.row].data
cell?.emailInbox.text = "\(email!)"
cell?.messaggioInbox.text = "\(message!)"
cell?.dataInbox.text = "\(date!)"
return cell!
}
return UITableViewCell()
}
And make sure that you have correct cell identifier for dequeuing.
extension ViewController : UITableViewDelegate,UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return listmoviesArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if tableView == moviesTableView {
let cell = tableView.dequeueReusableCell(withIdentifier: "MovieTableViewCell", for: indexPath) as! MovieTableViewCell
cell.delegate = self
cell.setupCell(listmoviesArray[indexPath.row],indexPath: indexPath)
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "MovieTableViewCell2", for: indexPath) as! MovieTableViewCell
cell.delegate = self
cell.setupCell(listmoviesArray[indexPath.row],indexPath: indexPath)
return cell
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if tableView == moviesTableView {
// Handle your selection for row.
} else {
//Handle your selection for row.
}
}
}
Above code produces the following output with 2 Tableview.

How to divide tableview to sections and give headlines

I have a tableview in my app.
I want to divide my cells into sections and give headlines.
Below I attach the picture (animals, birds headlines):
How do I do that in dynamic prototype cells?
My section data:
var sectionsData = [
"header",
"description",
"diagnoses",
"perscription",
"notes",
"addFaxHeadline",
"addFax",
"addEmailHeadline",
"addEmails",
"givePermissionHeadline",
"select answer"
]
Here is my cell at row function:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print ("indexPath: ", indexPath)
print ("indexPath: ", indexPath[0])
print ("-------")
if (sectionsData[indexPath[0]] == "header") {
let cell = tableView.dequeueReusableCell(withIdentifier: "headerCell", for: indexPath)
return cell
} else if (sectionsData[indexPath[0]] == "description") {
let cell = tableView.dequeueReusableCell(withIdentifier: "headerInfoCell", for: indexPath)
return cell
} else if (sectionsData[indexPath[0]] == "diagnoses") {
let cell = tableView.dequeueReusableCell(withIdentifier: "diagnosisCell", for: indexPath)
return cell
} else if (sectionsData[indexPath[0]] == "perscription") {
let cell = tableView.dequeueReusableCell(withIdentifier: "perscriptionCell", for: indexPath)
return cell
} else if (sectionsData[indexPath[0]] == "notes") {
let cell = tableView.dequeueReusableCell(withIdentifier: "notesCell", for: indexPath)
return cell
} else if (sectionsData[indexPath[0]] == "addFaxHeadline") {
let cell = tableView.dequeueReusableCell(withIdentifier: "addFaxCell", for: indexPath)
return cell
} else if (sectionsData[indexPath[0]] == "addFax") {
let cell = tableView.dequeueReusableCell(withIdentifier: "emailNameCell", for: indexPath)
return cell
} else if (sectionsData[indexPath[0]] == "addEmailHeadline") {
let cell = tableView.dequeueReusableCell(withIdentifier: "addEmailCell", for: indexPath)
return cell
} else if (sectionsData[indexPath[0]] == "addEmails") {
let cell = tableView.dequeueReusableCell(withIdentifier: "emailNameCell", for: indexPath)
return cell
} else if (sectionsData[indexPath[0]] == "givePermissionHeadline") {
let cell = tableView.dequeueReusableCell(withIdentifier: "permisionCell", for: indexPath)
return cell
} else if (sectionsData[indexPath[0]] == "select answer") {
let cell = tableView.dequeueReusableCell(withIdentifier: "selectAnswerCell", for: indexPath)
return cell
}
let cell = tableView.dequeueReusableCell(withIdentifier: "addFaxCell", for: indexPath)
// <<<< ???
return cell
}
and my numbers in row per section function:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
if (sectionsData[section] == "header") {
print ("returning 1 ?")
return 1
} else if (sectionsData[section] == "description") {
return 1
} else if (sectionsData[section] == "diagnoses") {
//return visitSummary.diagnoses.count
return 2
} else if (sectionsData[section] == "perscription") {
//return visitSummary.prescriptions.count
return 2
} else if (sectionsData[section] == "notes") {
return 1
} else if (sectionsData[section] == "addFaxHeadline") {
return 1
} else if (sectionsData[section] == "addFax") {
return faxAdded.count
} else if (sectionsData[section] == "addEmailHeadline") {
return 1
} else if (sectionsData[section] == "addEmails") {
return emailsAdded.count
} else if (sectionsData[section] == "givePermissionHeadline") {
return 1
} else if (sectionsData[section] == "select answer") {
return 1
} else {
return 0
}
return 0
}
and my number of sections:
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
print ("sectionsData.count: ", sectionsData.count)
return sectionsData.count
}
All of the tutorials I find is for one section and I have a very amount of sections to show.
How would I divide it and give them headlines?
Let's clean up your code first:
enum Section: Int, CaseIterable {
case header = 0, description, diagnoses, prescription, notes, addFaxHeadline, addFax, addEmailHeadline, addEmails, givePermissionHeadline, selectAnswer
}
var sectionData: [Section] = [
.header,
.description,
.diagnoses
...
]
override func numberOfSections(in tableView: UITableView) -> Int {
return sectionsData.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection sectionIndex: Int) -> Int {
let section = sectionData[sectionIndex]
switch section {
case .header:
return 1
case .description:
return 1
// ...
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let section = sectionData[sectionIndex]
switch section {
case .header:
let cell = tableView.dequeueReusableCell(withIdentifier: "headerCell", for: indexPath)
return cell
// ... etc
}
}
Now, we can return section title just using a String:
override func tableView(_ tableView: UITableView, titleForHeaderInSection sectionIndex: Int) -> String? {
let section = sectionData[sectionIndex]
switch section {
case .header:
return "Header title"
case .description:
return "Description title"
default:
return nil
}
}
Another option is to use tableView(:viewForHeaderInSection:) and tableView(:heightForHeaderInSection:):
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 44
}
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let view = UIView()
let label = UILabel()
view.addSubview(label)
// setup constraints accordingly
// setup title of the label
return view
}
You can also use a custom reusable header which would be registered and dequeued in the same way cells are dequeued.
var sectionsData = [
"header",
"description",
"diagnoses",
"prescription",
"notes",
"addFaxHeadline",
"addFax",
"addEmailHeadline",
"addEmails",
"givePermissionHeadline",
"select answer"
] // Your Array
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? //*Data Source method for header title*
{
// Safe cast
if let sectionArray = sectionsData as? [String]
{
return sectionArray[section]
}
// A fail safe
return "No Header"
}
Now I see. You are misunderstanding how these methods work. They are called for every cell in every section.
So, if you return 4 in numberOfSections, the method numberOfRowsInSection will be called 4 times, and sectionIndex (now called section in latest versions) will have the current section (0 to 3 in our example).
Therefore, if you want to call the second section "Birds" and let all the others nil, your code will look like this:
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if section == 1 {
return "Birds"
}
// Otherwise
return nil
}
Same thing for how many rows each section will have:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
// 1 row for the first section
return 1
} else if section == 1 {
// 3 rows for the second section
return 3
}
// 2 rows for every other section
return 2
}
And, finally, what cell will be used in each indexPath:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Figure out which row and section you are talking about
let row = indexPath.row
let section = indexPath.section
// Now you know where you are in the TableView, create the corresponding cell according to your project
// Start by dequeueing a custom cell using the identifier and forcing the cell to become your custom cell
let cell = tableView.dequeueReusableCell(withIdentifier: customCellID) as! CustomTableViewCell
// Do aditional setup to this cell
cell.textLabel?.text = "This is cell \(row) in section \(section)"
// Return the cell when you are ready
return cell
}
Remember Sulthan's suggestion to create an enum for your sections.

Use two UITableViews in one ViewController Swift

How can i create two UITableViews in one ViewController , I have one problem
The problem you need every single to Return is not within the condition and I have information for each Tableview
This message : " Missing return in a function expected to return 'Int' "
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if tableView == table_View {
return list.count
}
if tableView == table_View2 {
return list_2.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if tableView == table_View {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell_1") as! TableView_Cell
cell.la_view.text = list[indexPath.row]
cell.backgroundColor = UIColor(named: "Defeult")
return cell
}
if tableView == table_View2 {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell_2") as! TableView_Cell
cell.la_view2.text = list_2[indexPath.row]
cell.backgroundColor = UIColor(named: "Defeult")
return cell
}
}
The isse is, that e.g. numberOfRowsInSection has to return something in any case. In your implementation, you have two if statements, and you, but only you know that this is sufficient, because tableView can only be any of the two. Unfortuantely, the compiler does not know this. Therefore, you could either do it a simple way:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if tableView == table_View {
return list.count
}
return list_2.count
}
Note: same applies to cellForRowAt function
Maybe better:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if tableView == table_View {
return list.count
} else if tableView == table_View2 {
return list_2.count
}
assertionFailure("Unexpected tableView")
return 0
}
As a quick solution (please make sure to read the rest of the answer), you could do it like this:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return (tableView == table_View) ? list.count : list_2.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if tableView == table_View {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell_1") as! TableView_Cell
cell.la_view.text = list[indexPath.row]
cell.backgroundColor = UIColor(named: "Defeult")
return cell
}
if tableView == table_View2 { {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell_2") as! TableView_Cell
cell.la_view2.text = list_2[indexPath.row]
cell.backgroundColor = UIColor(named: "Defeult")
return cell
}
return UITableViewCell()
}
Important Tip:
Keep in mind that if you have to add two table views in the same view controller (which should be not a pretty cool idea), you could separate handling the dataSource and delegate for each table view in a different class (not the same view controller that contains the table views). Example:
In your view controller, set the dataSource as:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let table_View = UITableView()
let table_View2 = UITableView()
table_View.dataSource = SourceHandler1()
table_View2.dataSource = SourceHandler2()
}
}
Therefore, implement:
class SourceHandler1: NSObject, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return list.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell_1") as! TableView_Cell
cell.la_view.text = list[indexPath.row]
cell.backgroundColor = UIColor(named: "Defeult")
return cell
}
}
class SourceHandler2: NSObject, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
list_2.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell_2") as! TableView_Cell
cell.la_view2.text = list_2[indexPath.row]
cell.backgroundColor = UIColor(named: "Defeult")
return cell
}
}
That leads to avoid the issue of "massive" view controller and reduce the possibility of producing Spaghetti code.
change your code inside numberOfRowsInSection because of this delegate method required at least one Int value for a case. since you are using both value return in if condition, the error asking a value to return in else case.
So each case should return an Int value.
if tableView == table_View {
return list.count
}
else {
return list_2.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if tableView == table_View {
return list.count
}
if tableView == table_View2 {
return list_2.count
}
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if tableView == table_View {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell_1") as! TableView_Cell
cell.la_view.text = list[indexPath.row]
cell.backgroundColor = UIColor(named: "Defeult")
return cell
}
if tableView == table_View2 {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell_2") as! TableView_Cell
cell.la_view2.text = list_2[indexPath.row]
cell.backgroundColor = UIColor(named: "Defeult")
return cell
}
return UITableViewCell()
}

Displaying two reusable cells in tableview - Swift 3

I have two custom reusable table view cells in my table view. The first cell, I would like it to be present at all times. The second cell and beyond, are returning a count that is being passed from mysql database.
// return the amount of cell numbers
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return posts.count
}
// cell config
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row < 1 {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! InfoCell
//set the data here
return cell
} else {
let Postcell = tableView.dequeueReusableCell(withIdentifier: "PostCell", for: indexPath) as! PostCell
let post = posts[indexPath.row]
let image = images[indexPath.row]
let username = post["user_username"] as? String
let text = post["post_text"] as? String
// assigning shortcuts to ui obj
Postcell.usernameLbl.text = username
Postcell.textLbl.text = text
Postcell.pictureImg.image = image
return Postcell
}
} // end of function
My first cell is there and so are the post.count, but for some reason the posts.count is missing one post and I believe this is because of the first cell. Can anybody help me with this? thanks in advance.
You need to adjust the value returned from numberOfRowsInSection to account for the extra row. And you would need to adjust the index used to access values from your posts array to deal with the extra row.
But a much better solution is to use two sections. The first section should be your extra row and the second section would be your posts.
func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return 1
} else {
return posts.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! InfoCell
//set the data here
return cell
} else {
let Postcell = tableView.dequeueReusableCell(withIdentifier: "PostCell", for: indexPath) as! PostCell
let post = posts[indexPath.row]
let image = images[indexPath.row]
let username = post["user_username"] as? String
let text = post["post_text"] as? String
// assigning shortcuts to ui obj
Postcell.usernameLbl.text = username
Postcell.textLbl.text = text
Postcell.pictureImg.image = image
return Postcell
}
}