I'm implementing two table views on a single view controller in Swift 5. I realise this can be done with a single extension to the view controller by identifying the table views by name. However, I'd like to keep the code separate, and so I've done it as shown in this simple example here - this has involved looking at several examples on the web and so a general thank you to all who post these things! The main one is the answer by Girish Ghoda here:
Two tables on one view in swift
It all seems to work, but I'm wondering if I'm breaking any important rules...
There are two table views on the view controller, with simple constraints and outlets tableView1 and tableView2.
This is the ViewController.swift file
import UIKit
var array1 = ["one", "two", "three"]
var array2 = ["left", "right", "centre", "outside"]
class ViewController: UIViewController {
#IBOutlet weak var tableView1: UITableView!
#IBOutlet weak var tableView2: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
initTableViews()
}
var dataSource1: DataSource1!
var dataSource2: DataSource2!
func initTableViews() {
dataSource1 = DataSource1()
tableView1.dataSource = dataSource1
tableView1.delegate = dataSource1
dataSource2 = DataSource2()
tableView2.dataSource = dataSource2
tableView2.delegate = dataSource2
}
}
There are then two files:
TableViewClass1.swift
import UIKit
class DataSource1: NSObject, UITableViewDataSource, UITableViewDelegate {
override init(){
super.init()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return array1.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell1", for: indexPath)
cell.textLabel?.numberOfLines = 0
cell.textLabel?.text = array1[indexPath.row]
return cell
}
}
TableViewClass2.swift:
import UIKit
class DataSource2: NSObject, UITableViewDataSource, UITableViewDelegate {
override init(){
super.init()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return array2.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell2", for: indexPath)
cell.textLabel?.numberOfLines = 0
cell.textLabel?.text = array2[indexPath.row]
return cell
}
}
As I say, everything seems ok, so it may seem strange to ask about it, but while the approach seems to give simple code structure, I'm wondering if there is anything here that could lead to problems.
Many thanks,
Ian
It's fine, but if both classes are that redundant I would use one DataSource class with a convenience initializer and lazy instantiated dataSource properties
class DataSource: NSObject, UITableViewDataSource, UITableViewDelegate {
var array : [String]
let cellIdentifier : String
init(array: [String], cellIdentifier : String )
{
self.array = array
self.cellIdentifier = cellIdentifier
super.init()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return array.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath)
cell.textLabel?.numberOfLines = 0
cell.textLabel?.text = array[indexPath.row]
return cell
}
}
lazy var dataSource1: DataSource = {
return DataSource(array: array1, cellIdentifier: "cell1")
}()
lazy var dataSource2: DataSource = {
return DataSource(array: array2, cellIdentifier: "cell2")
}()
func initTableViews() {
tableView1.dataSource = dataSource1
tableView1.delegate = dataSource1
tableView2.dataSource = dataSource2
tableView2.delegate = dataSource2
}
Related
import UIKit
class editViewController: UIViewController, UITableViewDelegate, UITableViewDataSource{
#IBOutlet weak var loopTableView: UITableView!
//loop Data
var memberArr: [(String)] = []
var payArr: [(Int)] = []
var dateArr: [(String)] = []
var loopTypeArr: [(String)] = []
var holesArr: [(Int)] = []
override func viewDidLoad() {
super.viewDidLoad()
loopTableView.dataSource = self
loopTableView.delegate = self
// Do any additional setup after loading the view.
}
//Table view functions
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return payArr.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "loopCell"){
let loopCell = memberArr[indexPath.row]
cell.textLabel?.text = loopCell
return cell
//"\(dateArr[indexPath.row])\t\(memberArr[indexPath.row])"
}
return UITableViewCell()
}
}
I need to take price from table view cell
I need to get it another VC
I need to transfer price each time when user selected row
The problem is that I don't know how correctly get the value from tableView
MenueViewController:
import UIKit
class MenueViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var dishes: [Dish] = []
var totalSum = 0
override func viewDidLoad() {
super.viewDidLoad()
dishes = createArray()
tableView.delegate = self
tableView.dataSource = self
tableView.backgroundColor = .white
navigationItem.title = "Меню"
}
func createArray() -> [Dish] {
var tempDishes: [Dish] = []
let dish1 = Dish(image: UIImage.init(named: "plovKebab")!, title: "Плов Кебаб", price: 169, type: "Основные Блюда")
let dish2 = Dish(image: UIImage.init(named: "plovKebabShafran")!, title: "Плов Кебаб Шафран", price: 169, type: "Основные Блюда")
tempDishes.append(dish1)
tempDishes.append(dish2)
return tempDishes
}
}
extension MenueViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dishes.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let dish = dishes[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "MenueCell") as! MenueCell
cell.setDish(dish: dish)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let dish = dishes[indexPath.row]
totalSum += dish.price //the place where I tried to take price
print(totalSum)
}
}
VC where we need to take that price:
import UIKit
class OrderViewController: UIViewController {
#IBOutlet weak var totalPriceLabel: UILabel!
var totalPrice: MenueViewController?
var sum:Int?
override func viewDidLoad() {
super.viewDidLoad()
if let price = sum {
totalPriceLabel.text = String(totalPrice?.totalSum)
}
}
The value of sum is 0
How can I get the value?
Please note that the didSelectRowAtIndexpath will not get called before your performSegue. At this link, you can find a detailed explanation including a sample code about your problem and how it can be solved.
Also, there are multiple approaches listed below to solve your problem.
Use willSelectIndexpath and capture the indexpath to pass it in performSegue method
Use didSelectIndexpath and perform segue inside
Use only didSelectIndexpath and perform the navigation logic inside it with out segues
so I have a swift file with 3 different classes in it
import UIKit
class TableRow: UITableViewCell {
#IBOutlet var row: DataRow!
}
class DataRow: UIStackView {
// addnew stackView
var allData: [DataCell]!
}
protocol DataCellDelegate {
func getData(_ personData: [Person])
}
class DataCell: UIStackView {
weak var delegate: DataCellDelegate?
var cellData: [Person]?
let youngerPeople = cellData.filter({$0.age < 16})
delegate.getData(youngerPeople)
}
// and I have this VC Class
import UIKit
class MyViewController: UIViewController, UITableViewDataSource, DataCellDelegate {
func getData(_ personData: [Person]){
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: enter code hereIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableRow
}
}
//how do I get to the delegate on DataCell and set it as self on the
//MyViewController like this
// DataCell.delegate = self
// cos when I put a breakpoint on 1 getData(_ personData: [Person]) nothing happens
In your view controller you would want:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: indexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableRow
for dataCell in cell.row.allData {
dataCell.delegate = self
}
return cell
}
Additionally, it looks like you might want to make your youngerPeople property lazy:
let youngerPeople = {
let data = cellData.filter({$0.age < 16})
delegate.getData(data)
return data
}()
I have a ViewController on the storyboard.
TestViewController.swift
class TestViewController: UIViewController {
var users : Dictionary = [String:User]()
let customTableView : UITableView = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
customTableView.register(UITableViewCell.self, forCellReuseIdentifier: "userCell")
let customTable = CustomTable(users:users)
customTableView.delegate = customTable
customTableView.dataSource = customTable
view.addSubview(customTableView)
customTableView.translatesAutoresizingMaskIntoConstraints = false
customTableView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
customTableView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
customTableView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
customTableView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
}
}
CustomTable.swift
class CustomTable: NSObject, UITableViewDelegate, UITableViewDataSource {
var users : Dictionary = [String:User]()
var userNames = ["Steve", "Joe", "Bob"]
var userIds = [String]()
init(users: [String:User]) {
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("HEY")
return userNames.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print("HIHI")
let cell = tableView.dequeueReusableCell(withIdentifier: "userCell", for: indexPath)
let text = userNames[indexPath.row]
cell.textLabel?.text = text
return cell
}
}
numberOfRowsInSection - seems to fire
cellForRowAt - doesn't fire / print "HIHI", therefore, my table is empty.
I have moved creating the table to viewDidAppear, nothing.
I have searched tirelessly to find similar situations, but nothing seems to solve it. Does anything stick out to anyone or have done this successfully?
Declare customTable reference at class level. It becomes nil as soon as it leaves the scope of viewDidLoad method.
I have the following code to display two tables populated from two different arrays in one view:
#IBOutlet var RFTable: UITableView
func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {
}
override func viewDidLoad() {
super.viewDidLoad()
self.RFTable.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell")
}
func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int {
return self.RFArray.count;
}
func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
var cell:UITableViewCell = self.RFTable.dequeueReusableCellWithIdentifier("cell") as UITableViewCell
cell.textLabel.text = String(self.RFArray[indexPath.row])
return cell
}
#IBOutlet var IMProdTable: UITableView
func tableView2(IMProdTable: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {
}
override func viewDidLoad() {
super.viewDidLoad()
self.IMProdTable.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell2")
}
func tableView2(IMProdTable: UITableView!, numberOfRowsInSection section: Int) -> Int {
return self.IMProdArray.count;
}
func tableView2(IMProdTable: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
var cell2:UITableViewCell = self.IMProdTable.dequeueReusableCellWithIdentifier("cell2") as UITableViewCell
cell2.textLabel.text = String(self.IMProdArray[indexPath.row])
return cell2
}
I got the first table working, and then copied and pasted the text, replacing the array names and tableview names, and have hooked up the delegate and datasource. However Xcode displays 'invalid redeclaration of viewdidload' on the second (pasted) code. If I replace this to 'fund loadView() {' instead of viewdidload the app builds. When I test it though, both tables view exactly the same data which is the data in 'RFArray.' I am VERY new to coding and cannot see what I have done, please help.
#IBOutlet var RFTable: UITableView
#IBOutlet var IMProdTable: UITableView
func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {
}
override func viewDidLoad() {
super.viewDidLoad()
self.RFTable.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell")
self.IMProdTable.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell2")
}
func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int {
if tableView == RFTable {
return self.RFArray.count;
} else {
return self.IMProdArray.count;
}
}
func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
if tableView == RFTable {
var cell:UITableViewCell = self.RFTable.dequeueReusableCellWithIdentifier("cell") as UITableViewCell
cell.textLabel.text = String(self.RFArray[indexPath.row])
return cell
} else {
var cell2:UITableViewCell = self.IMProdTable.dequeueReusableCellWithIdentifier("cell2") as UITableViewCell
cell2.textLabel.text = String(self.IMProdArray[indexPath.row])
return cell2
}
}
Just a quick edit. You need to keep the delegate and datasource methods same and check which TableView instance is actually sending the message.
You cannot override the same method twice in a derived class.
First create two DataSource implemented classes
First Data source
class FirstDataSouce: NSObject,UITableViewDataSource,UITableViewDelegate {
var items: [String] = []
override init(){
super.init()
}
func setData(items:[String]){
self.items = items
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "RecentTableViewCell") as! RecentTableViewCell
cell.titleLabel.text = items[indexPath.row]
return cell
}
}
Second Data source
class SecondDataSouce: NSObject,UITableViewDataSource,UITableViewDelegate {
var items: [String] = []
override init(){
super.init()
}
func setData(items:[String]){
self.items = items
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "RecentTableViewCell") as! RecentTableViewCell
cell.titleLabel.text = items[indexPath.row]
return cell
}
}
Set datasource to tableview in ViewController
class ViewController: UIViewController{
#IBOutlet weak var tableView1: UITableView!
#IBOutlet weak var tableView2: UITableView!
var dataSource1: FirstDataSouce!
var dataSource2: SecondDataSouce!
func prepareTableViews(){
let items1 = [“a”,”b”,”c”]
dataSource1 = FirstDataSouce()
dataSource1.setData(items: items1)
self.tableView1.dataSource = dataSource1
self.tableView1.delegate = dataSource1
self.tableView1.register(SelectorTableViewCell.self,
forCellReuseIdentifier:
"TableViewCell")
self.tableView1.tableFooterView = UIView()
let items2 = [“1”,”2”,”3”]
dataSource2 = SecondDataSouce()
dataSource2.setData(items: items2)
self.recentTableView.dataSource = dataSource2
self.recentTableView.delegate = dataSource2
self.recentTableView.register(RecentTableViewCell.self,
forCellReuseIdentifier:
"TableViewCell")
self.recentTableView.tableFooterView = UIView()
}
}
Also Make Sure To reload each TableView After fetching data to TableviewCell.
e.g
#IBOutlet var RFTable: UITableView
#IBOutlet var IMProdTable: UITableView
override func viewDidLoad() {
super.viewDidLoad()
self.RFTable.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell1")
self.IMProdTable.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell2")
RFTable.reloadData()
IMProdTable.reloadData()
}