Reputation: 55
I want a UITableView to display the following data:
Instead of:
The code I've used is as follows:
// Data model class defining an Animal
class Animal {
// data members
var name: String
var type: String
// initializer
init(name: String, type: String) {
self.name = name
self.type = type
}
}
and
// View Controller
import UIKit
class AnimalsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
// an array of Animal(s)
var animals: [Animal] = [Animal("Cat", "Domestic"), Animal("Dog", "Domestic"), Animal("Lion", "Wild"), Animal("Deer", "Wild"), Animal("Tiger", "Wild")];
override func viewDidLoad() {
super.viewDidLoad();
}
// returns the number of rows to be present in table
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return animals.count
}
// returns each cell to be present in the table
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentfier = "animalbasic"
// uses a protype cell from storyboard
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentfier)
cell.textLabel.text = animals[indexPath.row].name
return cell
}
}
What do I need to change, in my code, in order to get the required output?(Assume an array is always sorted Animal.type wise)
Upvotes: 1
Views: 138
Reputation: 474
Suggestions:
use a struct for this type of things, rather than a class
for expendability I would use an enum
for the animal type, something like:
enum AnimalType: String {
case domestic = "Domestic"
case wild = "Wild"
}
Note: not actually needed to make it string, you can also have a function that based on the type would return a localised string, you can write something like
enum AnimalType: String {
case domestic, wild
var localizedString: String {
switch self {
case .domestic: return "Domestic"
case .wild: return "Wild"
}
}
Now your struct will look like this
struct Animal {
let name: String
let type: AnimalType
}
and now you are able to init it like this
let animal = Animal(name: "Cat", type: .domestic)
Now back your problem, I would suggest to keep the type in a section header rather than a cell, it would make more sense don't you think?
Create a variable
let sections = [.domestic, .wild]
now you have the count and it will also be easily extended
func numberOfSections(in tableView: UITableView) -> Int {
return sections.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let sectionType = sections[section]
switch sectionType {
case .domestic:
return animals.filter{ $0.type == .domestic }.count
case .wild:
return animals.filter{ $0.type == .wild }.count
default:
return 0
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let sectionType = sections[section]
switch sectionType {
case .domestic:
/// Configure your cell
case .wild:
/// Configure your cell
default:
assertionFailure("Should not reach in this situation")
return UITableViewCell()
}
}
ofcourse you will have to also configure the header for your sections:
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let sectionType = sections[section]
switch sectionType {
default: sectionType.localizedString
}
Your approach is also doable, but this would mean that at numberOfRowsInSection you will have to add the number of sections:
// returns the number of rows to be present in table
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return animals.count + sections.count
}
and then cellForRowAt
would be a pain to mentain, you would need to do some calculations to see what row is it, is it still wild domestic, is it wild yet
TLTR you would have a bunch of if, if else, if else, else
, and by only
adding one more type (dinosaur maybe) this pain will hit you back
Note: this code can be improved using a viewModel, but for the sake of being fast i skipped that part
Upvotes: 1
Reputation: 385650
You need to break your data into sections by type.
class AnimalsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var animals: [Animal] = [Animal("Cat", "Domestic"), Animal("Dog", "Domestic"), Animal("Lion", "Wild"), Animal("Deer", "Wild"), Animal("Tiger", "Wild")]
var sections: [[Animal]] = []
override func viewDidLoad() {
super.viewDidLoad();
var sectionForType = [String: [Animal]]()
for animal in animals {
if sectionForType[animal.type] == nil { sectionForType[animal.type] = [] }
sectionForType[animal.type]!.append(animal)
}
sections = sectionForType.keys.sorted().map({ sectionForType[$0]! })
}
func numberOfSections(in tableView: UITableView) -> Int {
return sections.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sections[section].count
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sections[section][0].type
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "animalbasic")!
cell.textLabel!.text = sections[indexPath.section][indexPath.row].name
return cell
}
}
Upvotes: 3