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

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

Related

UITableViewCell data not showing up in UITableViewController

I am having trouble debugging why my UITableview cell data isn't showing up in the UITableview. The UITableview currently displays blank when the user navigates to it. Data is correctly going into the cellForRowAt and into the function that sets the cell data.
Setting the cell data
class EventInboxTableViewCell: UITableViewCell {
#IBOutlet weak var eventNameLabel: UILabel!
#IBOutlet weak var eventCoverImageView: UIImageView!
#IBOutlet weak var eventStartLabel: UILabel!
#IBOutlet weak var eventEndLabel: UILabel!
var eventStartString = String()
var eventEndString = String()
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
func setEvent(_ event:Event) {
eventNameLabel?.text = event.eventName
if event.eventStart != nil {
let eventStartTS = event.eventStart
let eventStartDate = eventStartTS?.dateValue()
self.eventStartString = AppWideService.dateToStringShort(date: eventStartDate!)
}
if event.eventEnd != nil {
let eventEndTS = event.eventEnd
let eventEndDate = eventEndTS?.dateValue()
self.eventEndString = AppWideService.dateToStringShort(date: eventEndDate!)
}
print("Event inbox event \(eventStartString)")
print("Event inbox event \(eventEndString)")
eventStartLabel?.text = self.eventStartString
eventEndLabel?.text = self.eventEndString
guard let urlString = event.eventCoverUrl as? String else { return }
let url = URL(string: urlString)
guard url != nil else {
//Couldn't create url object
return
}
eventCoverImageView?.sd_setImage(with: url) { (image, error, cacheType, url) in
self.eventCoverImageView?.image = image
}}}
For some reason when I remove the ? from setting the label text it says the values like eventName or eventStartString etc are nil, but I have print statements that ensure they are not.
UITableView Datasource
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return retrievedEvents.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "EventInboxTableViewCell", for: indexPath) as! EventInboxTableViewCell
let event = retrievedEvents[indexPath.row]
cell.setEvent(event)
return cell
}
Registered the cell in viewDidLoad
tableView.register(EventInboxTableViewCell.self, forCellReuseIdentifier: "EventInboxTableViewCell")
The problem is the way the table view controller was being used.
If you design a View Controller (of any type) in Storyboard, and you want to use it, you cannot simply say:
let vc = EventInboxTableViewController()
you have to instantiate it from the storyboard:
if let vc = storyboard?.instantiateViewController(withIdentifier: "EventInboxTableViewController") as? EventInboxTableViewController {
navigationController?.pushViewControllerFromLeft(controller: vc)
}
So, in Storyboard, assign your custom class to your UITableViewController, and make sure to fill in the Storyboard ID field (with the string you are using in code as the Identifier).

how to use delegate to pass data to from one View Controller to another?

Im trying to pass data from one View controller to another using a delegate
right now im struggling to pass data from the CartVC to ModifyVC when pressing the modifyButton in the CartCell. This is modeled similar to a previous question that I asked before(see link below). Im just struggling to pass data to the ModifyVC since I keep getting an error saying Thread 1: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value and closes out the simulator
the modifybtn passes cell data for the cel that is selected in the CartVC
I dont want to use didSelectRowAt to pass the cell data since im using the modifyBtn in the CartCell to pass the data using the ModifyDelegate
I know that im close to my solution to making this work. Im just getting that one error that is preventing me from passing the data to the ModifyVC
How pass data from button in TableViewCell to View Controller?
class CartViewController: UIViewController, UITableViewDelegate, UITableViewDataSource{
var selectedProduct: Items!
var modifyItems: Cart?
var cart: [Cart] = []
var groupedItems: [String: [Cart]] = [:]
var brands: [String] = []
#IBOutlet weak var cartTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) { //segue code for delegate
if let vc = segue.destination as? ModifyViewController {
vc.modifyItems = self.modifyItems
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return brands.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let brand = brands[section]
return groupedCartItems[brand]!.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cartCell = tableView.dequeueReusableCell(withIdentifier: "CartCell") as! CartCell
let brand = brands[indexPath.section]
let itemsToDisplay = groupedItems[brand]![indexPath.row]
cartCell.configure(withCartItems: itemsToDisplay.cart)
cartCell.modifyDelegate = self
cartCell.modifyItems = self.modifyItems
return cartCell
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let cartHeader = tableView.dequeueReusableCell(withIdentifier: "CartHeader") as! CartHeader
let headerTitle = brands[section]
cartHeader.brandName.text = "Brand: \(headerTitle)"
return cartHeader
}
}
class ModifyViewController: UIViewController {
private var counterValue = 1
var lastSelectedWeightButton = RoundButton()
var modifyItems: Cart!
#IBOutlet weak var price1: UILabel!
#IBOutlet weak var price2: UILabel!
#IBOutlet weak var price3: UILabel!
#IBOutlet weak var weight1: UILabel!
#IBOutlet weak var weight2: UILabel!
#IBOutlet weak var weight3: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let formatter = NumberFormatter()
formatter.maximumFractionDigits = 2
formatter.numberStyle = .decimal
price1.text = "$\(formatter.string(for: modifyItems.cart.price1)!)" // getting the error right here that causes the simulator to close out and prevents me from viewing the modify VC
price2.text = "$\(formatter.string(for: modifyItems.cart.price2)!)"
price3.text = "$\(formatter.string(for: modifyItems.cart.price3)!)"
weight1.text = modifyItems.cart.weight1
weight2.text = modifyItems.cart.weight2
weight3.text = modifyItems.cart.weight3
}
}
side note: The CartVC cells data is populated from the HomeVc when an item is selected it posted as a cell in the CartVC. the Items class populates the cells in the HomeVC.
Update following line in cellForRowAt function:
cartCell.modifyItems = self.modifyItems
to this:
cartCell.modifyItems = itemsToDisplay

How would I be able to pass an image when data is passed from one View Controller to another?

How would I be able to show an image from HomeViewController to the CartViewController
I have the code setup in my cells to where the data passes from one VC to another,
Im trying to present the image when the data is passed
How would I be able to show the image when data is passed from the HomeVC to the CartVC after the atcBtn is pressed
all the data in my labels passes fine its just the image data that fails to pass
I have tried a few ways from stack but I still keep getting error codes on presenting the image in the CartVC
class CartViewController: UIViewController {
var items: Items!
#IBOutlet weak var cartTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
cartTableView.dataSource = self
cartTableView.delegate = self
}
}
extension CartViewController: UITableViewDataSource, UITableViewDelegate {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return Cart.currentCart.cartItems.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CartCell", for: indexPath) as! CartCell
let cart = Cart.currentCart.CartItems[indexPath.row]
cell.store.text = cart.items.store
cell.lblMealName.text = (cart.items.name)
cell.lblSubTotal.text = "$\(cart.items.cost)"
cell.imageUrl.image = cart.imageUrl // can't figure out how to get this to code to work since it is an Image to String issue
return cell
class CartCell: UITableViewCell {
#IBOutlet weak var lblMealName: UILabel!
#IBOutlet weak var imageUrl: UIImageView!
#IBOutlet weak var lblSubTotal: UILabel!
#IBOutlet weak var lblWeight: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
}
The code you posted doesn't quite match up:
In cellForRowAt in CartViewController, for example, you are using CartCell but your code is setting:
cell.store.text = cart.items.store
but there is no store label / property in your posted CartCell.
However, since you are doing very similar things with HomeCell class, just take the same approach for CartCell.
Something along these lines:
class CartCell: UITableViewCell {
#IBOutlet weak var lblMealName: UILabel!
#IBOutlet weak var imageUrl: UIImageView!
#IBOutlet weak var lblSubTotal: UILabel!
#IBOutlet weak var lblWeight: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
func configure(withItems items: Items) {
//store.text = cart.items.store
lblMealName.text = (items.name)
lblSubTotal.text = "$\(items.cost)"
imageUrl.sd_setImage(with: URL(string: items.imageUrl))
}
}
and change `cell
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CartCell", for: indexPath) as! CartCell
let cart = Cart.currentCart.CartItems[indexPath.row]
//cell.store.text = cart.items.store
//cell.lblMealName.text = (cart.items.name)
//cell.lblSubTotal.text = "$\(cart.items.cost)"
//cell.imageUrl.image = cart.imageUrl // can't figure out how to get this to code to work since it is an Image to String issue
cell.configure(withItem: cart)
return cell
}
This appears to be where the problem is
cell.imageUrl.image = cart.imageUrl // can't figure out how to get
this to code to work since it is an Image to String issue
and as you noted, that code doesn't really make sense... If you're storing a url (a string) in your cart object, then you can't cast that to an image with cell.imageUrl.image, right?
You would need to assign it to the url
cell.imageUrl = cart.imageUrl
Of course that will just pass the url to the cell. The cell would then need some intelligence to get that associated image from the url.
Some pseudo code for your CartCell class...
cell.store.text = cart.items.store
cell.lblMealName.text = (cart.items.name)
cell.lblSubTotal.text = "$\(cart.items.cost)"
cell.setImageUrlAndDisplayImage( cart.imageUrl )
and then the function in the CartCell class
func setImageUrlAndDisplayImage( imageUrl: URL) {
self.setImage(with: URL(string: imageUrl))
}
or of course, you could just assign the image directly to the CartCell image property if it has one.
cell.store.text = cart.items.store
cell.lblMealName.text = (cart.items.name)
cell.lblSubTotal.text = "$\(cart.items.cost)"
cell.the_image = UIImage(with: URL(string: cart.imageUrl))
The above is just pseudo code since we don't know what you Cart class or CartCell class looks like.

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 :)

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

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

Resources