How to populate UITableView with 2 sections - swift

My issue is with the NumberOfRowsInSection that should read two values.
let sections = ["Apples", "Oranges"]
override func numberOfSections(in tableView: UITableView) -> Int {
return sections.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//Option A)
if apples.count & oranges.count != 0 {
return apples.count & oranges.count
} else {
return 0
}
//Option B)
if sections[0] == "Apples" {
return apples.count
}
if sections[1] == "Oranges"{
return oranges.count
}
return 0
}
None of the options work, crashing because it doesn't get the amount of things of one of each other... on the CellForRowAt.
Plus somebody knows how to get the title of those sections as well?

numberOfRows will be called for each section so you need to return the correct value based on the current section being queried:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return section == 0 ? apples.count : oranges.count
}
You need a similar pattern in cellForRowAt and all of the other data source/delegate methods:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", indexPath: indexPath
if indexPath.section == 0 {
cell.titleLabel.text = apples[indexPath.row]
} else {
cell.titleLabel.text = oranges[indexPath.row]
}
return cell
}
This is simplified and makes lots of assumptions but it gives you the right idea.
For the headers, you need:
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sections[section]
}
This answer also assumes you just have the two sections based on two array variables. This is far from ideal. You really should have a single data structure with all of the proper structure. Then no assumptions need to be made. You can add more sections and rows and not have to change any table view code.

Related

Swift tableview 'method heightForHeaderInSection' makes section text raise

When heightForHeaderInSection and/or heightForFooterInSection is called, the section text raises above it's first row. I attached a screenshot to show the issue. How can I increase the section spacing without the section header being affected?
#IBOutlet weak var tableView: UITableView!
let fruits =
["apple","pear","orange","caramel","peach","fruit","fruits"]
let numbers = ["1", "2"]
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
func tableView(_ tableView: UITableView, numberOfRowsInSection
section: Int) -> Int {
if section == 0 {
return fruits.count
} else {
return numbers.count
}
}
func tableView(_ tableView: UITableView, titleForHeaderInSection
section: Int) -> String? {
if section == 0 {
return "Animals"
} else {
return "Numbers"
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath:
IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier:
"cell1", for: indexPath)
if indexPath.section == 0 {
cell.textLabel?.text = fruits[indexPath.row]
} else {
cell.textLabel?.text = numbers[indexPath.row]
}
return cell
}
These are the 2 methods in question:
func tableView(_ tableView: UITableView, heightForHeaderInSection
section: Int) -> CGFloat {
return 100
}
func tableView(_ tableView: UITableView, heightForFooterInSection
section: Int) -> CGFloat {
return 100
}
You are setting the height of the header to 100. See here:
func tableView(_ tableView: UITableView, heightForHeaderInSection
section: Int) -> CGFloat {
return 100 // Instead try returning 30
}
func tableView(_ tableView: UITableView, heightForFooterInSection
section: Int) -> CGFloat {
return 100 // If you are not using a footer - return .zero
}
I got the answer from another post.
Just add:
tableView.sectionHeaderTopPadding = 100 // or whatever number you want.
There's also another way.
Click on of these ->
You can choose Grouped or the rounded design 'Inset Grouped'
After you can use the method 'heightForHeaderInSection' to select the specific section/s.

How to insert one Cell into the first section of UITableView Swift

I have created a UITableView with two sections. Now I am trying to populate one section. When I do action, my program populates both sections at the same time with the same text. What I am trying to do is to populate the first section, and then eventually move one of the cell from Section 1 to Section 2. So far I got here:
extension ListVC: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
randomCount = notes.count
return notes.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: CustomNoteCell.reuseId) as! CustomNoteCell
cell.set(with: notes[indexPath.row])
return cell
}
func numberOfSections(in tableView: UITableView) -> Int {
sections.count
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if section < sections.count {
return sections[section]
}
return nil
}
}
I believe it needs to happen in cellForRowAt but I have no clue how to specify section. Any help and clues appreciated. Many thanks ;)
Change the code of numberOfRowInSection to this:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 { //first section
randomCount = notes.count
return notes.count
}else{
return 0
}
}

Filter items to section

I want to filter items with property isCompleted = true to section with name Completed and non completed items to ToDo. How to render items?
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return manager.tasks.filter({$0.isCompleted == false}).count
} else {
return manager.tasks.filter({$0.isCompleted}).count
}
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
switch section {
case 0:
return "ToDo"
case 1:
return "Completed"
default:
return nil
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: Keys.cell.rawValue, for: indexPath) as! ToDoCell
let currentItem = manager.tasks[indexPath.row]
cell.titleLabel.text = currentItem.taskName
cell.descriptionLabel.text = currentItem.description
if manager.tasks[indexPath.row].description?.isEmpty ?? false {
cell.descLabelBottomConstraint.constant = 0
}
let accessoryType: UITableViewCell.AccessoryType = currentItem.isCompleted ? .checkmark : .none
cell.accessoryType = accessoryType
return cell
}
I guess I need to filter items into two different arrays? But which way is the most correct?
Never filter things in numberOfRowsInSection. Don't do that, this method is called very often.
Create a model
struct Section {
let title : String
var items : [Task]
}
Declare the data source array
var sections = [Section]()
In viewDidLoad populate the array and reload the table view
sections = [Section(title: "ToDo", items: manager.tasks.filter{!$0.isCompleted}),
Section(title: "Completed", items: manager.tasks.filter{$0.isCompleted})]
tableView.reloadData()
Now the datasource methods become very clean (and fast)
override func numberOfSections(in tableView: UITableView) -> Int {
return sections.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sections[section].items.count
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sections[section].title
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: Keys.cell.rawValue, for: indexPath) as! ToDoCell
let currentItem = sections[indexPath.section].items[indexPath.row]
cell.titleLabel.text = currentItem.taskName
cell.descriptionLabel.text = currentItem.description
if currentItem.description?.isEmpty ?? false {
cell.descLabelBottomConstraint.constant = 0
} // you have to add an else clause to set the constraint to the default value
cell.accessoryType = currentItem.isCompleted ? .checkmark : .none
return cell
}
It would be still more efficient to filter the items O(n) with a partition algorithm
let p = manager.tasks.partition(by: { $0.completed })
sections = [Section(title: "ToDo", items: Array(manager.tasks[p...])),
Section(title: "Completed", items: Array(manager.tasks[..<p]))]
tableView.reloadData()
You can create 2 properties completed and notCompleted in the Manager and use them as dataSource of the tableView.
class Manager {
lazy var completed: [Task] = {
return tasks.filter({ !$0.isCompleted })
}()
lazy var notCompleted: [Task] = {
return tasks.filter({ $0.isCompleted })
}()
}
UITableViewDataSource and UITableViewDelegate methods,
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return section == 0 ? manager.notCompleted.count : manager.completed.count
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return section == 0 ? "Todo" : "Completed"
}
You want your original dataSource to be an array of the 2 different arrays (one with completed and one that is not completed.) [[]]
I found This one that seems pretty solid. However, it returns an dictionary, but i rewrote it slightly for you:
extension Sequence {
func group<U: Hashable>(by key: (Iterator.Element) -> U) -> [[Iterator.Element]] {
return Dictionary.init(grouping: self, by: key).map({$0.value})
}
}
This way when you are in title header or cellForRowAt you can call it by manager.task[indexPath.section][indexPath.item]

How to add select from map option in tableViewCell, Google map, Swift

I am using google map on my current project. To search places when I click on a textField a tableView appear below and I am populating the cell with GMSAutocompletePrediction object. So far its working fine and no problem so far. Now I want to add a cell like "Select From Map", when someone tap one the cell a marker will appear in the map. I can able to put the marker and additional map feature but how can I add that last cell "Select From Map" ? My code below:
var myFilterPredictions = [GMSAutocompletePrediction]()
Then:
func didAutocomplete(with predictions: [GMSAutocompletePrediction]) {
self.myFilterPredictions.removeAll()
print("GMSAutocompletePrediction *********")
for i in predictions {
self.myFilterPredictions.append(i)
}
tableView.reloadData()
}
The populating the tableView
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myFilterPredictions.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style:.subtitle, reuseIdentifier:"searchCell")
let predictions = myFilterPredictions[indexPath.row]
cell.textLabel?.attributedText = predictions.attributedPrimaryText
cell.detailTextLabel?.attributedText = predictions.attributedFullText
return cell
}
My search result looks similar to the image below:
I want to add "Select from map" in the last cell.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0{
return myFilterPredictions.count
}
else{
return 1
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = UITableViewCell()
if indexPath.section == 1{
cell.textLabel = ""Select From Map""
return cell
}else{
cell = UITableViewCell(style:.subtitle, reuseIdentifier:"searchCell")
let predictions = myFilterPredictions[indexPath.row]
cell.textLabel?.attributedText = predictions.attributedPrimaryText
cell.detailTextLabel?.attributedText = predictions.attributedFullText
return cell
}
}
You also use Group table for that
func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
// Removes extra padding in Grouped style
return CGFloat.leastNormalMagnitude
}
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
// Removes extra padding in Grouped style
return CGFloat.leastNormalMagnitude
}

How can I populate multiple tableview sections when the contents of the arrays are of different types?

I'm having a bit of a polymorphism issue. I've got three arrays aList, bList and cList that contain different types of equipment "A", "B", "C".
I want to populate a tableviewcontroller with 3 sections for aList, bList, cList. But i'm struggling a bit when I get to:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
The problem i'm having is that ideally i'd have three arrays within one array eg. section = [aList, bList, cList).
In my singleton "A", "B", "C" are structs and represent a type of equipment and so I changed them into classes and subclassed them from a "Equipment" superclass and then tried to create an array of the superclass "Equipment" but that didn't work either.
Advice would be great.
class EquipmentListController: UITableViewController {
var model = SingletonModel.sharedInstance
var aList:[SingletonModel.A] = []
var bList:[SingletonModel.B] = []
var cList:[SingletonModel.C] = []
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
aList = model.AList
bList = model.BList
cList = model.CList
sections.append(aList)
sections.append(bList)
sections.append(cList)
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 3
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch section{
case 1:
self.tableView.rowHeight = 70
return aList.count
case 2:
return bList.count
case 3:
return cList.count
default:
return 0
}
}
My problem is below with the line let equipment: SingletonModel.A (or B or C) - basically i've got three separate arrays and each is a different type.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
guard let cell = tableView.dequeueReusableCell(withIdentifier: "equipmentcell", for: indexPath) as? EquipmentCell else
{
}
let equipment: SingletonModel.A (or B or C) = changeDataSource(indexPath: indexPath as NSIndexPath)
cell.equipmentTitle.text = equipment.equipmentName
cell.accessoryType = .disclosureIndicator
return cell
}
func changeDataSource(indexPath: NSIndexPath) -> SingletonModel.A, B or C
{
var equipment:SingletonModel.A (B or C)
equipment = A, B or C [indexPath.row]
return equipment
}
func tableView(tableView: UITableView!, titleForHeaderInSection section: Int) -> String!
{
switch section
{
case 1:
return "A"
case 2:
return "B"
case 3:
return "C"
default:
return nil
}
}
override func tableView(_ tableView: UITableView,
heightForHeaderInSection section: Int) -> CGFloat
{
return 40
}
}
Thanks
First you need to fix section numbering: sections are numbered from zero, not from one:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch section {
case 0:
return aList.count
case 1:
return bList.count
case 2:
return cList.count
default:
return -1 // Fail fast
}
}
Next, change the way you set row height:
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
// Rows in section 0 are taller
return indexPath.section == 0 ? 70.0 : 50.0;
}
Now you can set your cell up using A, B, or C:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "equipmentcell", for: indexPath) as? EquipmentCell else
{
return nil
}
var equipment : BaseOfABC = nil
switch indexPath.section {
case 0:
equipment = SingletonModel.A
case 1:
equipment = SingletonModel.B
case 2:
equipment = SingletonModel.C
default:
assert(indexPath.section < 3, "Unknown section")
}
cell.equipmentTitle.text = equipment.equipmentName
cell.accessoryType = .disclosureIndicator
return cell
}
In indexPath you have the row and the section of the cell :
indexPath.row
indexPath.section
You can switch on indexPath.section
For each (equipment) type you can create your own cell type.
In
tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)
Switch on indexPath.section, create the matching cell type and set its attributes from the matching (equipment) array.