UITableView Custom Class not showing results - swift

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.

Related

UITableView button color issue

I have some questions to answer with Yes or Not in a UITableView, each option (yes/no) is it's own button in the cell.
First, I have to handle the click event, because I have to change button's backgroundColor on click and I have to add the clicked row to create another struct with the answers.
My actual code is like this:
extension MyViewController: UITableViewDelegate{
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//click row
}
}
extension MyViewController: UITableViewDataSource{
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return questions.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellAnswers", for: indexPath) as! MyTableViewCell
//iterate here? and how can i do that?
cell.actionYes = {
cell.buttonYes.backgroundColor = .blue
cell.buttonNo.backgroundColor = .darkGray
}
cell.actionNot = {
cell.buttonYes.backgroundColor = .darkGray
cell.buttonNo.backgroundColor = .blue
}
let question = questions[indexPath.row]
cell.question = question
return cell
}
}
Like you see, I have clousures to handle click event and change backgroundColor, but if i click one button, the rest change too.
and my TableViewCell:
import UIKit
class MyTableViewCell: UITableViewCell{
#IBOutlet weak var question: UILabel!
#IBOutlet weak var buttonYes: UIButton!
#IBOutlet weak var buttonNo: UIButton!
var actionYes: (() -> Void)? = nil
var actionNo: (() -> Void)? = nil
#IBAction func answerYes(_ sender: Any) {
actionYes?()
}
#IBAction func answerNo(_ sender: Any) {
actionNo?()
}
var question: QuestionResponse!{
didSet{
updateUI()
}
}
func updateUI(){
question.text = question.value
}
}
I don't have any idea how to iterate the the cells to fill the correct color on buttons.
What you are facing is the result of cell reusing that UTableView does.
Whenever a cell is out of the screen, (after scrolling) gets added to a queue to be reused. That's why you need to call dequeueReusableCell(withIdentifier:for:) function to get a cell that you need to prepare for displaying your data. That means that a UITableViewCell instance may already have some changes left over from the last time that has been used.
The correct way to deal with a UTableView is to keep whatever you change in you datasource. For example you can add an answer property to your QuestionResponse model, like this:
enum Answer {
case yes, no
}
struct QuestionResponse {
var answer: Answer?
}
And store there the user's answer. Also you have to make sure that you always set the background color of your buttons to avoid unwanted leftover colors from previous usage of the same cell instance:
extension MyViewController: UITableViewDataSource{
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return questions.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellAnswers", for: indexPath) as! MyTableViewCell
//iterate here? and how can i do that?
cell.actionYes = {
cell.buttonYes.backgroundColor = .blue
cell.buttonNo.backgroundColor = .darkGray
}
cell.actionNot = {
cell.buttonYes.backgroundColor = .darkGray
cell.buttonNo.backgroundColor = .blue
}
let question = questions[indexPath.row]
switch question.answer {
case .yes:
cell.buttonYes.backgroundColor = .blue
cell.buttonNo.backgroundColor = .darkGray
case .no:
cell.buttonYes.backgroundColor = .darkGray
cell.buttonNo.backgroundColor = .blue
default:
cell.buttonYes.backgroundColor = .darkGray
cell.buttonNo.backgroundColor = .darkGray
}
cell.question = question
return cell
}
}

How to get value from table view cell

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

UITableView is working fine in simulator but not in my physical device

I am currently building an Application which shows all the faculties that are available in my college. I have the data in a .plist file which I have read and populated in the UITableView. I am currently trying to show some details such as prefix and Name in the cell which when clicked on Expands to show more details about the faculty.
My TableViewController Code is shown below:
struct cellData {
var opened = Bool()
var Name = String()
var Prefix = String()
var sectionData = [String]()
}
class TableViewController: UITableViewController {
//MARK: - Initialize variables
var dataManager = DataManager()
private var header = [String]()
private var facultyDetails = [[String: String]]()
var tableViewData = [cellData]()
#IBOutlet var facultyTableView: UITableView!
//MARK: - view did load
override func viewDidLoad() {
super.viewDidLoad()
for data in dataManager.allSheet1s() {
let dataInCell = cellData(opened: false, Name: data.item2!,
Prefix: data.item1!, sectionData [data.item0!,
data.item2!,
data.item1!,
data.item3!,
data.item4!,
data.item5!,
data.item6!,
data.item7!,
data.item8!,
data.item9!,
data.item10!
])
tableViewData.append(dataInCell)
facultyTableView.register(UINib(nibName: "CustomCell", bundle: nil), forCellReuseIdentifier: "CustomCell")
}
}
// MARK: - Table view delegate
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return tableViewData.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell") as! CustomCell
if indexPath.row == 0 {
cell.EmpID.text = tableViewData[indexPath.section].sectionData[0]
cell.Name.text = tableViewData[indexPath.section].sectionData[1]
cell.Prefix.text = tableViewData[indexPath.section].sectionData[2]
cell.SchoolName.text = tableViewData[indexPath.section].sectionData[3]
cell.BuildingName.text = tableViewData[indexPath.section].sectionData[4]
cell.FloorNo.text = tableViewData[indexPath.section].sectionData[5]
cell.CabinLocation.text = tableViewData[indexPath.section].sectionData[6]
cell.RoomNo.text = tableViewData[indexPath.section].sectionData[7]
cell.CabinNo.text = tableViewData[indexPath.section].sectionData[8]
cell.IntercomNumber.text = tableViewData[indexPath.section].sectionData[9]
cell.Email.text = tableViewData[indexPath.section].sectionData[10]
return cell
}
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if tableViewData[indexPath.section].opened == true {
tableViewData[indexPath.section].opened = false
let sections = IndexSet.init(integer: indexPath.section)
tableView.reloadSections(sections, with: .right)
}
else {
tableViewData[indexPath.section].opened = true
let sections = IndexSet.init(integer: indexPath.section)
tableView.reloadSections(sections, with: .left)
}
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if tableViewData[indexPath.section].opened == true {
return 400
}
else {
return 70
}
}
}
Simulator
Physical Device
Right now you can see in the simulator that it works perfectly fine. But when I load it up in the physical device the height of each cell in the rows are clipping through the other cells below it.
Modify your cell for row code by adding cell.clipsToBounds = true
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell") as! CustomCell
cell.clipsToBounds = true
if indexPath.row == 0 {
cell.EmpID.text = tableViewData[indexPath.section].sectionData[0]
cell.Name.text = tableViewData[indexPath.section].sectionData[1]
cell.Prefix.text = tableViewData[indexPath.section].sectionData[2]
cell.SchoolName.text = tableViewData[indexPath.section].sectionData[3]
cell.BuildingName.text = tableViewData[indexPath.section].sectionData[4]
cell.FloorNo.text = tableViewData[indexPath.section].sectionData[5]
cell.CabinLocation.text = tableViewData[indexPath.section].sectionData[6]
cell.RoomNo.text = tableViewData[indexPath.section].sectionData[7]
cell.CabinNo.text = tableViewData[indexPath.section].sectionData[8]
cell.IntercomNumber.text = tableViewData[indexPath.section].sectionData[9]
cell.Email.text = tableViewData[indexPath.section].sectionData[10]
return cell
}
return cell
}
And override a method
override func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 70
}

Is this ok when implementing multiple table views in Swift5?

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
}

My TableView's not changed after reload data

I wrote a class that let me create multiple tableView simply. When I call this class for the first time, everything work well. But when I change some data, and reload the table, nothing changed.
Sample code:
class TestViewController: UIViewController {
var arrData = ["a","b","c"]
var myTableView: MyTableView?
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
myTableView = MyTableView(table: tableView, data: arrData)
}
#IBAction func buttonTapped(_ sender: UIButton) {
arrData = ["d","e","f"]
myTableView!.tableView.reloadData() //=> Not change anything
}
}
class MyTableView: NSObject, UITableViewDataSource {
var tableView: UITableView
var data: Array<String>
init(table: UITableView, data: Array<String>) {
self.data = data
self.tableView = table
super.init()
self.tableView.dataSource = self
self.tableView.register(MyTableViewCell.self, forCellReuseIdentifier: "myCell")
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.data.count
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) as! MyTableViewCell
cell.textLabel!.text = self.data[indexPath.row]
return cell
}
}
class MyTableViewCell : UITableViewCell {
//something here
}
When the view was loaded, the table has 3 rows: a,b,c. When I tap the button, nothing changed (expected: d,e,f)
Please help me!
Swift arrays are copied by value so the line self.data = data will take a copy of your array. Later changing the array contents of the source will not be reflected in the copy in your MyTableView.
You'll need to pass the array over again and take a second copy to update the table, e.g. write a method in MyTableView similar to the following:-
func setNewValues(data: Array<String>)
{
self.data = data
self.tableView.reloadData()
}
and call that from your buttonTapped function, i.e.:
#IBAction func buttonTapped(_ sender: UIButton) {
arrData = ["d","e","f"]
myTableView!.setNewValues(data: arrData)
}
Be careful with the force-unwrapped myTableView though - I'd replace that '!' with '?'.