Issue properly triggering a view controller to open when a UITableView row is pressed? - ios

How can I properly trigger a view controller to open when a UITableView row is pressed?
I need to allow the user to go back from the view controller back to the tableView and allow them to select the same or a different tableView row.
The problem I am currently having is the application crashes when selecting the same row more than once after returning back from ViewController that opens when selecting on one of the rows: scheduledDelivery
Currently, this is the code I have:
import UIKit
class ScheduledCell: UITableViewCell {
#IBOutlet weak var ETALabel: UILabel!
#IBOutlet weak var cellStructure: UIView!
#IBOutlet weak var scheduledLabel: UILabel!
#IBOutlet weak var testingCell: UILabel!
#IBOutlet weak var pickupLabel: UILabel!
#IBOutlet weak var deliveryLabel: UILabel!
#IBOutlet weak var stopLabel: UILabel!
#IBOutlet weak var topBar: UIView!
}
class ToCustomerTableViewController: UITableViewController, UIGestureRecognizerDelegate {
var typeValue = String()
var driverName = UserDefaults.standard.string(forKey: "name")!
var structure = [AlreadyScheduledStructure]()
override func viewDidLoad() {
super.viewDidLoad()
fetchJSON()
//Disable delay in button tap
self.tableView.delaysContentTouches = false
tableView.tableFooterView = UIView()
}
private func fetchJSON() {
guard let url = URL(string: "https://example.com/example/example"),
let value = driverName.addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed)
else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = "driverName=\(value)".data(using: .utf8)
URLSession.shared.dataTask(with: request) { data, _, error in
guard let data = data else { return }
do {
self.structure = try JSONDecoder().decode([AlreadyScheduledStructure].self,from:data)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
catch {
print(error)
}
}.resume()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return structure.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "scheduledID", for: indexPath) as! ScheduledCell
let portfolio = structure[indexPath.row]
cell.stopLabel.text = "Stop \(portfolio.stop_sequence)"
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let portfolio = structure[indexPath.row]
let controller = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "scheduledDelivery")
print(portfolio.customer)
let navTitle = portfolio.customer
UserDefaults.standard.set(navTitle, forKey: "pressedScheduled")
controller.navigationItem.title = navTitle
navigationController?.pushViewController(controller, animated: true)
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 200.0
}
}
Notice how in cellForRowAt I am setting the cell as a dequeueReusableCell which might be why the app is crashing sometimes when selecting the same cell more than once
let cell = tableView.dequeueReusableCell(withIdentifier: "scheduledID"
I have also noticed that if the tableView rows are reloaded on viewDidAppear it does not crash as often, but of course, this is a terrible solution.
Error I get:
'NSInternalInconsistencyException', reason: 'Attempted to dequeue
multiple cells for the same index path, which is not allowed. If you
really need to dequeue more cells than the table view is requesting,
use the -dequeueReusableCellWithIdentifier: method

According to the crash replace
let cell = tableView.dequeueReusableCell(withIdentifier: "scheduledID", for: indexPath) as! ScheduledCell
with
let cell = tableView.dequeueReusableCell(withIdentifier: "scheduledID") as! ScheduledCell

Related

I m getting 4 times values of same data in tableview cell, so kindly help to pass the data with one value

import UIKit
class ViewController: UIViewController,UITableViewDelegate, UITableViewDataSource, pass {
var array = [String]()
#IBOutlet weak var tblView: UITableView!
#IBOutlet weak var btnPush: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return array.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath) as!TableViewCell
cell.lblName.text = array[indexPath.row]
cell.lblFullName.text = array[indexPath.row]
cell.lblRollno.text = array[indexPath.row]
cell.lblClass.text = array[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 200
}
#IBAction func btnPush(_ sender: UIButton) {
let vc = storyboard?.instantiateViewController(withIdentifier: "SecondVC") as!SecondVC
vc.mac = self
self.navigationController?.pushViewController(vc, animated: true)
}
func Datapass(Name: String, FullName Address: String, Rollno: String, Class: String) {
self.array.append(Name)
self.array.append(Address)
self.array.append(Rollno)
self.array.append(Class)
tblView.reloadData()
}
}
import UIKit
protocol pass {
func Datapass(Name:String, FullName:String, Rollno:String, Class:String)
}
class SecondVC: UIViewController {
#IBOutlet weak var textFldName: UITextField!
#IBOutlet weak var txtFldFullName: UITextField!
#IBOutlet weak var txtFldRollno: UITextField!
#IBOutlet weak var txtFldClass: UITextField!
var mac:pass?
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func btnAdd(sender: UIButton) {
mac?.Datapass(Name: textFldName.text!, FullName: txtFldFullName.text!, Rollno: txtFldRollno.text!, Class: txtFldClass.text!)
self.navigationController?.popViewController(animated: true)
}
}
your adding data to your array the wrong way. your adding 4 parameter
and your array will be like this:
array: 0:name|1:Address|2:Rollno|3:Class
and when your reading this from table every time you map this:
cell.lblName.text = array[0]
cell.lblFullName.text = array[0]
cell.lblRollno.text = array[0]
cell.lblClass.text = array[0]
every time table only reads one element of array for 4 times! you are not changing indexpath.row , it will change it's number after loading 1 cell.
the right way to do this is code below:
struct DbModel{
var name:String
var address:String
var rollno:String
var `class`:String
}
var array :[DbModel] = []
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath) as! TableViewCell
let model = array[indexPath.row]
cell.lblName.text = model.name
cell.lblFullName.text = model.address
cell.lblRollno.text = model.rollno
cell.lblClass.text = model.class
return cell
}
func Datapass(Name: String, FullName Address: String, Rollno: String, Class: String) {
let model = DbModel(name: Name, address: Address, rollno: Rollno, class: Class)
self.array.append(model)
tblView.reloadData()
}

Why is this not displaying information in a UITableView?

I would really appreciate any help on this. I'm very new to coding, and have no luck implementing this feature so far. I'm looking to populate a UITableViewCell with information gathered from Firestore, namely: title, username and content. I've been able to print the 'title' array successfully, but have not been able to actually populate this into the cells.
This is the HomeViewController, where my UITableView is:
class HomeViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var logoutButton: UIButton!
var postArray: [String] = []
var documents: [DocumentSnapshot] = []
let db = Firestore.firestore()
let currentUserID = Auth.auth().currentUser?.uid
// Find the UserIDs of people following
// Where Field for those UserIDs in "Posts"
override func viewDidLoad() {
super.viewDidLoad()
getFollowingPosts()
configureTableView()
}
func getFollowingPosts() {
let searchForFollowing = db.collection("users").document(currentUserID!).collection("Following")
searchForFollowing.getDocuments { (snapshot, error) in
for documents in snapshot!.documents {
let followedUID = documents.get("uid")
print(followedUID!)
self.db.collection("posts").whereField("uid", isEqualTo: followedUID!).getDocuments { (querySnapshot, error) in
for documents in querySnapshot!.documents {
let uid = documents.get("uid") as! String
let title = documents.get("Title") as! String
let ProfilePictureURL = documents.get("ProfilePictureURL") as! String
let username = documents.get("username") as! String
let content = documents.get("Content") as! String
self.postArray.append(title)
print(self.postArray)
}
self.tableView.reloadData()
}
}
}
}
func configureTableView() {
tableView.delegate = self
tableView.dataSource = self
tableView.register(PostTableViewCell.self, forCellReuseIdentifier: "PostCell")
// remove separators for empty cells
tableView.tableFooterView = UIView()
// remove separators from cells
tableView.separatorStyle = .none
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
postArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "PostCell", for: indexPath) as! PostTableViewCell
let post = postArray[indexPath.row]
return cell
}
}
This is my PostTableViewCell:
class PostTableViewCell: UITableViewCell {
#IBOutlet weak var usernameLabel: UILabel!
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var contentLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
addSubview(usernameLabel)
addSubview(titleLabel)
addSubview(contentLabel)
}
}
If anyone could help, this would be massively appreciated. Like I said, I've been struggling a lot with this one.
You don't seem to be setting the data onto anything in the cell.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "PostCell", for: indexPath) as! PostTableViewCell
let post = postArray[indexPath.row]
cell.titleLabel.text = post
return cell
}
Also, modify the register method if you're using nib
func configureTableView() {
//...
tableView.register(UINib(nibName: "PostCell", bundle: nil), forCellReuseIdentifier: "PostCell")
//...
}
Note: Make sure that the nib file has nib's identifier set as "PostCell".

How pass the value of a selected cell to another ViewController?

Essentially, I have the following UITableViewController that contains custom tableView cells with labels in them. When the cell is selected I would like the value of the cell to be passed to the next view controller where I am using it in an HTTP POST response.
What can be added to didSelectRowAt to pass the value of the selected cell to the view controller presented?
Perhaps as a variable?
The following is my code:
import UIKit
class ScheduledCell: UITableViewCell {
#IBOutlet weak var ETALabel: UILabel!
#IBOutlet weak var cellStructure: UIView!
#IBOutlet weak var scheduledLabel: UILabel!
#IBOutlet weak var testingCell: UILabel!
#IBOutlet weak var pickupLabel: UILabel!
#IBOutlet weak var deliveryLabel: UILabel!
#IBOutlet weak var stopLabel: UILabel!
#IBOutlet weak var topBar: UIView!
}
class ToCustomerTableViewController: UITableViewController, UIGestureRecognizerDelegate {
var typeValue = String()
var driverName = UserDefaults.standard.string(forKey: "name")!
var structure = [AlreadyScheduledStructure]()
override func viewDidLoad() {
super.viewDidLoad()
fetchJSON()
//Disable delay in button tap
self.tableView.delaysContentTouches = false
tableView.tableFooterView = UIView()
}
private func fetchJSON() {
guard let url = URL(string: "https://example.com/example/example"),
let value = driverName.addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed)
else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = "driverName=\(value)".data(using: .utf8)
URLSession.shared.dataTask(with: request) { data, _, error in
guard let data = data else { return }
do {
self.structure = try JSONDecoder().decode([AlreadyScheduledStructure].self,from:data)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
catch {
print(error)
}
}.resume()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return structure.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "scheduledID", for: indexPath) as! ScheduledCell
let portfolio = structure[indexPath.row]
cell.stopLabel.text = "Stop \(portfolio.stop_sequence)"
cell.testingCell.text = portfolio.customer
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let portfolio = structure[indexPath.row]
let controller = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "scheduledDelivery")
print(portfolio.customer)
controller.navigationItem.title = navTitle
navigationController?.pushViewController(controller, animated: true)
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 200.0
}
}
Create public variables for the data which you want to pass to the scheduledDelivery controller.Then set them inside didselect delegate method. Let say if you want to pass portfolio.customer. Declare following public variable on scheduledDelivery controller.
public var portfilio:String?
Then set value to that variable from the didselect method like this,
let portfolio = structure[indexPath.row]
let controller = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "scheduledDelivery")
controller.portfilio = portfolio.customer
controller.navigationItem.title = navTitle
navigationController?.pushViewController(controller, animated: true)
add a portfolio variable to your next ViewController
class scheduledDeleivery: UIViewController{
var customer:String? //suposing this is customer type
then in
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let portfolio = structure[indexPath.row]
let controller = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "scheduledDelivery") as! shcheduledDeleivery
controller.customer = porfolio.customer //here is the customer you need to pass to next viewcontroller
print(portfolio.customer)
controller.navigationItem.title = navTitle
navigationController?.pushViewController(controller, animated: true)
}
You can store the cell's data as a variable and then in prepare for segue function pass it to the other ViewController. If you call this in prepare for segue it will automatically do it every time you try to access that segue.
var nameOfVar : String = ""
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
var secondVC = segue.destination as! YourSecondViewController
secondVC.variable = nameOfVar
}
Hope I've helped :)

How to pass selected row value as a public variable available to multiple view controllers?

How to have pass the value of a selected tableView to a public variable that can be accessed by multiple ViewControllers? Currently, in didSelectRowAt, I define the row selected as portfolio doing let portfolio = structure[indexPath.row] Now how can I save this value to perhaps some sort of variable that makes it avalible to multiple view controller?
I don't just mean pushing the value to whichever view controller is being presented when the cell is pressed, I need it be available to view controller past the .pushViewController.
In the past I tried using userdefaults, but this is not appropriate for values that are constantly changing and are not permanen.
import UIKit
class ScheduledCell: UITableViewCell {
#IBOutlet weak var ETALabel: UILabel!
#IBOutlet weak var cellStructure: UIView!
#IBOutlet weak var scheduledLabel: UILabel!
#IBOutlet weak var testingCell: UILabel!
#IBOutlet weak var pickupLabel: UILabel!
#IBOutlet weak var deliveryLabel: UILabel!
#IBOutlet weak var stopLabel: UILabel!
#IBOutlet weak var topBar: UIView!
}
class ToCustomerTableViewController: UITableViewController, UIGestureRecognizerDelegate {
var typeValue = String()
var driverName = UserDefaults.standard.string(forKey: "name")!
var structure = [AlreadyScheduledStructure]()
override func viewDidLoad() {
super.viewDidLoad()
fetchJSON()
//Disable delay in button tap
self.tableView.delaysContentTouches = false
tableView.tableFooterView = UIView()
}
private func fetchJSON() {
guard let url = URL(string: "https://example.com/example/example"),
let value = driverName.addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed)
else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = "driverName=\(value)".data(using: .utf8)
URLSession.shared.dataTask(with: request) { data, _, error in
guard let data = data else { return }
do {
self.structure = try JSONDecoder().decode([AlreadyScheduledStructure].self,from:data)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
catch {
print(error)
}
}.resume()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return structure.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "scheduledID", for: indexPath) as! ScheduledCell
let portfolio = structure[indexPath.row]
cell.stopLabel.text = "Stop \(portfolio.stop_sequence)"
cell.testingCell.text = portfolio.customer
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let portfolio = structure[indexPath.row]
let controller = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "scheduledDelivery")
print(portfolio.customer)
controller.navigationItem.title = navTitle
navigationController?.pushViewController(controller, animated: true)
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 200.0
}
}
You can use a function to pass an optional Value inside an extension, try the following:
From what I understood you want to pass values from your viewController and be able to get it from any other viewController..
extension UIViewController {
func passData(row: Int?) -> Int? {
var myValue = Int()
if row != nil {
myValue = row!
}
return myValue
}
}
in this function you can Pass the value you want and also retrieve it.
to pass data into the function simply use this :
passData(row: indexPath.row)
and if you want to retrieve the value of it from another viewController use this:
let myValue = passData(row: nil)
this way you could get the Data you pass from another viewController..
if that didn't work for you I'd suggest you use UserDefaults ..
I hope this could solve your problem.
You can use NSNotificationCenter and post value after selection and every subscribed controller will received a new value. For more info read this NSNotificationCenter addObserver in Swift

Can't call object from another class

I have a table view with expanding cells. The expanding cells come from a xib file. In the class of the table is where all of the code is that controls the expansion and pulling data from plist. I'm trying to add a close button but only want it to show when the cell is expanded. As it stands, I can't reference the button to hide it because it's in another class. Here is how I am trying to access it:
import UIKit
class SecondPolandViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var customTableViewCell:CustomTableViewCell? = nil
var items = [[String:String]]()
override func viewDidLoad() {
super.viewDidLoad()
**REFERENCING CLASS**
customTableViewCell = CustomTableViewCell()
let nib = UINib.init(nibName: "CustomTableViewCell", bundle: nil)
self.tableView.register(nib, forCellReuseIdentifier: "cell")
self.items = loadPlist()
}
func loadPlist()->[[String:String]]{
let path = Bundle.main.path(forResource: "PolandResourceList", ofType: "plist")
return NSArray.init(contentsOf: URL.init(fileURLWithPath: path!)) as! [[String:String]]
}
var selectedIndex:IndexPath?
var isExpanded = false
func didExpandCell(){
self.isExpanded = !isExpanded
self.tableView.reloadRows(at: [selectedIndex!], with: .automatic)
}
}
extension SecondPolandViewController:UITableViewDataSource, UITableViewDelegate{
***HIDING BUTTON***
let button = customTableViewCell?.closeButton
button?.isHidden = true
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.selectedIndex = indexPath
self.didExpandCell()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomTableViewCell
cell.selectionStyle = .none
let item = self.items[indexPath.row]
cell.titleLabel.text = item["title"]
cell.shortLabel.text = item["short"]
cell.otherImage.image = UIImage.init(named: item["image"]!)
cell.thumbImage.image = UIImage.init(named: item["image"]!)
cell.longLabel.text = item["long"]
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let height = UIScreen.main.bounds.height
if isExpanded && self.selectedIndex == indexPath{
//return self.view.frame.size.height * 0.6
return 400
}
return 110
//return height * 0.2
}
}
This does not hide it though.
Here is the xib that I am calling from if it helps. It is probably simple, I am just a newly self taught developer.
import UIKit
class CustomTableViewCell: UITableViewCell {
#IBOutlet weak var closeButton: UIImageView!
#IBOutlet weak var otherImage: UIImageView!
#IBOutlet weak var thumbImage: UIImageView!
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var shortLabel: UILabel!
//#IBOutlet weak var longLabel: UITextView!
#IBOutlet weak var longLabel: UITextView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
//let width = UIScreen.main.bounds.width
//let height = UIScreen.main.bounds.height
//thumbImage.frame.size.width = height * 0.19
//thumbImage.frame.size.height = height * 0.19
}
}
It seems like that you just need to add these lines into cellForRowAt:indexPath method:
if indexPath == selectedIndexPath {
cell.closeButton.isHidden = false
} else {
cell.closeButton.isHidden = true
}
You may add them right before return line
The normal iOS answer for this is a delegate, but you could get away with a simple closure in this case.
In CustomTableViewCell, add
public var closeTapped: ((CustomTableViewCell) -> ())?
Then in that class, when close is tapped, call
self.closeTapped?(self)
In the VC, in cellForRowAt,
cell.closeTapped = { cell in
// do what you want with the VC
}
For delegates, this might help: https://medium.com/#jamesrochabrun/implementing-delegates-in-swift-step-by-step-d3211cbac3ef
The quick answer to why to prefer delegates over the closure is that its a handy way to group a bunch of these together. It's what UITableViewDelegate is (which you are using). Also, it's a common iOS idiom.
I wrote about this here: https://app-o-mat.com/post/how-to-pass-data-back-to-presenter for a similar situation (VC to VC communication)

Resources