iOS multiple JSON file data transfer through segue in Swift - ios

I am trying to make a country weather forecast app, I have vc1 and vc2.
The JSON file to be parsed in vc2 exists for each country, and when the table view cell of vc1 is clicked, we try to implement parsing the JSON file of the country in vc2.
However, I do not know how to pass the JSON file name from vc1 to vc2 through segue.
When passing from vc1 to vc2 using segue, the variable is nil. Is there any solution?
Thanks for reading.
vc1
class MainViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var countries = [Countries]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
let jsonDecoder = JSONDecoder()
guard let dataAsset = NSDataAsset(name: "countries")
else {
return
}
do {
countries = try jsonDecoder.decode([Countries].self, from: dataAsset.data)
} catch {
print(error.localizedDescription)
}
tableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return countries.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MainCell", for: indexPath)
let country: Countries = countries[indexPath.row]
cell.imageView?.image = UIImage(named: "flag_" + country.asset_name)
cell.textLabel?.text = country.korean_name
return cell
}
// Data Transfer
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let nextViewController: SecondViewController = segue.destination as? SecondViewController else {
return
}
guard let cell: UITableViewCell = sender as? UITableViewCell else {
return
}
func name(indexPath: IndexPath) {
let country: Countries = countries[indexPath.row]
nextViewController.title = cell.textLabel?.text
nextViewController.secondAssetName = country.asset_name
}
}
}
vc2
class SecondViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var weathers = [Weather]()
var secondAssetName: String?
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.delegate = self
self.tableView.dataSource = self
let jsonDecoder = JSONDecoder()
guard let dataAsset = NSDataAsset(name: secondAssetName ?? " ") else {
return
}
do {
weathers = try jsonDecoder.decode([Weather].self, from: dataAsset.data)
} catch {
print(error.localizedDescription)
}
tableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return weathers.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: CustomTableViewCell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomTableViewCell
let weather: Weather = weathers[indexPath.row]
switch weather.state {
case 10:
cell.cellImageView?.image = UIImage(named: "sunny.png")
case 11:
cell.cellImageView?.image = UIImage(named: "cloudy.png")
case 12:
cell.cellImageView?.image = UIImage(named: "rainy.png")
case 13:
cell.cellImageView?.image = UIImage(named: "snowy.png")
default:
return cell
}
cell.cityNameLabel.text = weather.city_name
cell.temperatureLabel.text = String(weather.celsius)
cell.rainfallProbabilityLabel.text = String(weather.rainfall_probability)
return cell
}
}

In VC1, when do you navigate to VC2?
If you have connected the VC1 and VC2 in the storyboard please remove that.
You need to call the "performSegueWithIdentifier" method when the user selects a cell.
For this, you need to implement tableview's didSelectRowAtIndexPath. In this method, you need to call the performSegueWithIdentier method to navigate to vc2.
Also,
In the prepare for segue method, you have this code.
func name(indexPath: IndexPath) {
let country: Countries = countries[indexPath.row]
nextViewController.title = cell.textLabel?.text
nextViewController.secondAssetName = country.asset_name
}
Why do you have it as function? and you are not calling the name function here?
You can move the following code outside the name(index path:) function.
Like so :
// Data Transfer
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let nextViewController: SecondViewController = segue.destination as? SecondViewController else {
return
}
guard let cell: UITableViewCell = sender as? UITableViewCell else {
return
}
let country: Countries = countries[indexPath.row]
nextViewController.title = cell.textLabel?.text
nextViewController.secondAssetName = country.asset_name
}

Related

TableViewCell data transfer but data late arrived in iOS

I am implementing a function that sends the title of the cell to the JSON file name of the next controller when clicking on a data table cell.
The data passes well, but the data arrives one by one late. If you click the first cell, the data is not gone, and if you click the second cell, the contents of the first cell are transferred.
Where do I adjust data going late one by one? Any ideas?
vc1
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let nextViewController: SecondViewController = segue.destination as? SecondViewController else {
return
}
guard let cell: UITableViewCell = sender as? UITableViewCell else {
return
}
nextViewController.title = cell.textLabel?.text
nextViewController.secondAssetName = jsonName
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let country: Countries = countries[indexPath.row]
jsonName = country.asset_name
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return countries.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MainCell", for: indexPath)
let country: Countries = countries[indexPath.row]
cell.imageView?.image = UIImage(named: "flag_" + country.asset_name)
cell.textLabel?.text = country.korean_name
return cell
}
// Data Transfer
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let nextViewController: SecondViewController = segue.destination as? SecondViewController else {
return
}
guard let cell: UITableViewCell = sender as? UITableViewCell else {
return
}
nextViewController.title = cell.textLabel?.text
nextViewController.secondAssetName = jsonName
}
}
vc2
class SecondViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var weathers = [Weather]()
var secondAssetName: String?
#IBOutlet weak var tableView: UITableView!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let jsonDecoder = JSONDecoder()
guard let dataAsset = NSDataAsset(name: secondAssetName ?? "") else {
return
}
do {
weathers = try jsonDecoder.decode([Weather].self, from: dataAsset.data)
} catch {
print(error.localizedDescription)
}
tableView.reloadData()
}
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.delegate = self
self.tableView.dataSource = self
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return weathers.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: CustomTableViewCell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomTableViewCell
let weather: Weather = weathers[indexPath.row]
switch weather.state {
case 10:
cell.cellImageView?.image = UIImage(named: "sunny.png")
case 11:
cell.cellImageView?.image = UIImage(named: "cloudy.png")
case 12:
cell.cellImageView?.image = UIImage(named: "rainy.png")
case 13:
cell.cellImageView?.image = UIImage(named: "snowy.png")
default:
return cell
}
cell.cityNameLabel.text = weather.city_name
cell.temperatureLabel.text = String(weather.celsius)
cell.rainfallProbabilityLabel.text = String(weather.rainfall_probability)
return cell
}
}
Add breakpoints to your code. You should see where the problem is. prepare(for: sender:) is being called before tableView(_: didSelectRowAt:), so the first time you tap a cell, jsonName is nil during prepare, then it gets set during didSelect. The second time you tap it, jsonName has the value from the first tap, then it gets updated after.
Put all of your logic in one place. Remove the didSelect method, and update prepare like so:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let nextViewController: SecondViewController = segue.destination as? SecondViewController else {
return
}
guard let cell: UITableViewCell = sender as? UITableViewCell else {
return
}
guard let indexPath = tableView.indexPath(for: cell) else {
return
}
let country: Countries = countries[indexPath.row]
nextViewController.title = country.korean_name
nextViewController.secondAssetName = country.asset_name
}

Error setting UILabel text in custom UITableViewCell

I am quite new to Swift programming, but I am having trouble setting UILabel text in my UITableView class for individual UITableViewCell instances.
I have created a custom subclass of UITableViewCell called PizzaTableViewCell and a custom UITableView class called PizzaListTableViewController. I am trying to populate the UITableView instance with data from an array, which is being populated from an API call to my node.js server.
I have included my UITableView subclass, custom UITablveViewCell class, the struct for the data, and a link to a screenshot of the Simulator loading what I have done. Any help is greatly appreciated!
I have verified that the data is being put in the array with no issues, as I can print the contents after the call to fetchInventory method. I have been able to set a single textLabel with
cell.textLabel?.text = pizzas[indexPath.row].name
along with an image in the array with:
cell.imageView?.image = pizzas[indexPath.row].image
but I have 2 more labels that I need in each cell which I cannot set. I have checked my IBOutlets and Storyboard identifiers, and they match the code.
class PizzaListTableViewController: UITableViewController {
var pizzas: [Pizza] = []
override func viewDidLoad() {
super.viewDidLoad()
//title you will see on the app screen at the top of the table view
navigationItem.title = "Drink Selection"
tableView.register(PizzaTableViewCell.self, forCellReuseIdentifier: "Pizza")
//tableView.estimatedRowHeight = 134
//tableView.rowHeight = UITableViewAutomaticDimension
fetchInventory { pizzas in
guard pizzas != nil else { return }
self.pizzas = pizzas!
print(self.pizzas)
//self.tableView.reloadData()
//print(self.pizzas)
DispatchQueue.main.async { [weak self] in
self?.tableView.reloadData()
}
}
} //end of viewDidLoad
private func fetchInventory(completion: #escaping ([Pizza]?) -> Void) {
Alamofire.request("http://127.0.0.1:4000/inventory", method: .get)
.validate()
.responseJSON { response in
guard response.result.isSuccess else { return completion(nil) }
guard let rawInventory = response.result.value as? [[String: Any]?] else { return completion(nil) }
let inventory = rawInventory.compactMap { pizzaDict -> Pizza? in
var data = pizzaDict!
data["image"] = UIImage(named: pizzaDict!["image"] as! String)
//print(data)
//print("CHECK")
print("Printing each item: ", Pizza(data: data))
//printing all inventory successful
return Pizza(data: data)
}
completion(inventory)
}
}
#IBAction func ordersButtonPressed(_ sender: Any) {
performSegue(withIdentifier: "orders", sender: nil)
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
//PRINTING ROWS 0 TWICE in console
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("ROWS", pizzas.count)
return self.pizzas.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: PizzaTableViewCell = tableView.dequeueReusableCell(withIdentifier: "Pizza", for: indexPath) as! PizzaTableViewCell
//cell.backgroundColor = Services.baseColor
//cell.pizzaImageView?.image = pizzas[indexPath.row].image
//THESE WORK BUT ARE A STATIC WAY OF SETTING THE CELLS
//CAN ONLY SET THE SELL WITH A SINGLE TEXT LABEL FROM THE DATA ARRAY
cell.imageView?.image = pizzas[indexPath.row].image
cell.textLabel?.text = pizzas[indexPath.row].name
//cell.textLabel?.text = pizzas[indexPath.row].description
//cell.textLabel?.text = "$\(pizzas[indexPath.row].amount)"
// cell.name?.text = pizzas[indexPath.row].name
// cell.imageView?.image = pizzas[indexPath.row].image
// cell.amount?.text = "$\(pizzas[indexPath.row].amount)"
// cell.miscellaneousText?.text = pizzas[indexPath.row].description
//print(cell.name?.text! as Any)
print(cell.imageView as Any)
return cell
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100.0
} //END OF
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "pizzaSegue", sender: self.pizzas[indexPath.row] as Pizza)
} //END OF override func tableView
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "pizzaSegue" {
guard let vc = segue.destination as? PizzaViewController else { return }
vc.pizza = sender as? Pizza
}
} //END OF override preppare func
}
class PizzaListTableViewController: UITableViewController {
var pizzas: [Pizza] = []
override func viewDidLoad() {
super.viewDidLoad()
//title you will see on the app screen at the top of the table view
navigationItem.title = "Drink Selection"
tableView.register(PizzaTableViewCell.self, forCellReuseIdentifier: "Pizza")
//tableView.estimatedRowHeight = 134
//tableView.rowHeight = UITableViewAutomaticDimension
fetchInventory { pizzas in
guard pizzas != nil else { return }
self.pizzas = pizzas!
print(self.pizzas)
//self.tableView.reloadData()
//print(self.pizzas)
DispatchQueue.main.async { [weak self] in
self?.tableView.reloadData()
}
}
} //end of viewDidLoad
private func fetchInventory(completion: #escaping ([Pizza]?) -> Void) {
Alamofire.request("http://127.0.0.1:4000/inventory", method: .get)
.validate()
.responseJSON { response in
guard response.result.isSuccess else { return completion(nil) }
guard let rawInventory = response.result.value as? [[String: Any]?] else { return completion(nil) }
let inventory = rawInventory.compactMap { pizzaDict -> Pizza? in
var data = pizzaDict!
data["image"] = UIImage(named: pizzaDict!["image"] as! String)
//print(data)
//print("CHECK")
print("Printing each item: ", Pizza(data: data))
//printing all inventory successful
return Pizza(data: data)
}
completion(inventory)
}
}
#IBAction func ordersButtonPressed(_ sender: Any) {
performSegue(withIdentifier: "orders", sender: nil)
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
//PRINTING ROWS 0 TWICE in console
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("ROWS", pizzas.count)
return self.pizzas.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: PizzaTableViewCell = tableView.dequeueReusableCell(withIdentifier: "Pizza", for: indexPath) as! PizzaTableViewCell
//cell.backgroundColor = Services.baseColor
//cell.pizzaImageView?.image = pizzas[indexPath.row].image
//THESE WORK BUT ARE A STATIC WAY OF SETTING THE CELLS
//CAN ONLY SET THE SELL WITH A SINGLE TEXT LABEL FROM THE DATA ARRAY
cell.imageView?.image = pizzas[indexPath.row].image
cell.textLabel?.text = pizzas[indexPath.row].name
//cell.textLabel?.text = pizzas[indexPath.row].description
//cell.textLabel?.text = "$\(pizzas[indexPath.row].amount)"
// cell.name?.text = pizzas[indexPath.row].name
// cell.imageView?.image = pizzas[indexPath.row].image
// cell.amount?.text = "$\(pizzas[indexPath.row].amount)"
// cell.miscellaneousText?.text = pizzas[indexPath.row].description
//print(cell.name?.text! as Any)
print(cell.imageView as Any)
return cell
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100.0
} //END OF
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "pizzaSegue", sender: self.pizzas[indexPath.row] as Pizza)
} //END OF override func tableView
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "pizzaSegue" {
guard let vc = segue.destination as? PizzaViewController else { return }
vc.pizza = sender as? Pizza
}
} //END OF override preppare func
}
struct Pizza {
let id: String
let name: String
let description: String
let amount: Float
//let amount: String
let image: UIImage
init(data: [String: Any]) {
//print("CHECK:: pizza.swift")
self.id = data["id"] as! String
self.name = data["name"] as! String
// self.amount = data["amount"] as! Float
self.amount = ((data["amount"] as? NSNumber)?.floatValue)!
self.description = data["description"] as! String
self.image = data["image"] as! UIImage
}
}
As noted above, I have been able to print the contents of the data array with beer names, pictures, descriptions and etc. I have tried to print to console
print(cell.name?.text)
after setting
cell.name?.text = pizzas[indexPath.row].name
but it prints nil and this is a problem. I have been stuck with this for about 2 weeks!
IBOutlets screenshot:
I think i found your Problem, let me explain
What you are doing here is you have a custom UITableViewCell defined in the Storyboard in a Controller named "Root View Controller" which is not your PizzaListTableViewController to put it simply
And as you said you have absolutely no issue regarding the IBOutlets
Now when you say
tableView.register(PizzaTableViewCell.self, forCellReuseIdentifier: "Pizza")
In Your PizzaListTableViewController you are not linking it with the UI of the cell rather just the Code (This is only used when there is no xib of the cell)
Now what you can do to solve this
Solution # 1
Move/Copy your UI of the PizzaTableViewCell to PizzaListTableViewController in the storyboard from your "Root View Controller"
Make sure you add a Reuse Identifier in the Attribute Inspector of the cell in the storyboard
remove tableView.register(PizzaTableViewCell.self, forCellReuseIdentifier: "Pizza") this wont give you an error this time as it will automatically get register
Make sure all the IBOutlets are connected
Solution # 2
create a separate Nib (xib) of the cell
and now you have to register the cell here like
tableView.register(UINib(nibName: "PizzaTableViewCell", bundle: Bundle.main), forCellReuseIdentifier: "PizzaCell")
Hope this helps.
Try this
cell.name?.text = ...
cell.amount?.text = ...
cell.miscellaneousText?.text = ...
cell.pizzaImageView?.image = ...
If it still does not work then make sure your cell and your outlets are not null when setting its value. Hope it helps !
There is something definitely strange going on with your setup.
If you try to name the IBOutlets with the same name as the UITableViewCell default property it'll throw an error. The fact that you were able to set those names and build successfully is strange.
From the screenshot above you can see what happens when I attempted to do this.
Make sure your Table View Controller class is set in the storyboard.
Make sure your Table View Cell class is set in the storyboard.
Make sure that all your outlets are properly connected.
Make sure your Table View Cell Identifier is provided in the storyboard.
My Table View Controller Subclass
My Table View Cell Subclass
cell.imageView?.image and cell.textLabel?.text are optional properties of the table view itself. They are not the properties of the custom cell that you designed.
You use tableView.register(PizzaTableViewCell.self, forCellReuseIdentifier: "Pizza") when you have designed a table view cell in XIB. But as you have designed the cell in the storyboard itself you should set the cell reuse identifier and cell class in the storyboard.
I hope this will help you out.

Pass Data (Label) from TableViewCell to another ViewController

I want to pass the Label from a TableViewCell to a ViewController when I click on the Cell. In the end it should be like twitter, that if you click on a cell with a Label you get passed on a detailViewController with the same Label init.
My Code doesn't work as I just get the blanket Label from the Storyboard...
import UIKit
import Firebase
class JobTableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var valueToPass:String!
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
// Arvice return to count jobs
return jobs.count
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("You selected cell #\(indexPath.row)!")
// Get Cell Label
let indexPath = tableView.indexPathForSelectedRow!
let currentCell = tableView.cellForRow(at: indexPath)! as UITableViewCell
let valueToPass = currentCell.textLabel?.text
print("value: \(valueToPass)")
performSegue(withIdentifier: "toDetails", sender: valueToPass)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let cell = tableView.dequeueReusableCell(withIdentifier: "JobCell", for: indexPath) as! JobTableViewCell
let job = jobs[indexPath.row]
cell.job = job
//spacing
cell.contentView.backgroundColor = UIColor.clear
let whiteRoundedView : UIView = UIView(frame: CGRect(x: 10, y: 8, width: self.view.frame.size.width - 20, height: 120))
whiteRoundedView.layer.backgroundColor = CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [0.36, 0.39, 0.40, 1.0])
whiteRoundedView.layer.masksToBounds = false
whiteRoundedView.layer.cornerRadius = 2.0
whiteRoundedView.layer.shadowOffset = CGSize(width: 0, height: 0)
whiteRoundedView.layer.shadowOpacity = 0.0
cell.contentView.addSubview(whiteRoundedView)
cell.contentView.sendSubview(toBack: whiteRoundedView)
cell.emojiLabel.text = cell.emojiString
return cell
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toDetails" {
let destinationViewController = segue.destination as! JobDetailViewController
destinationViewController.valueToPass = (sender as? String)!
}
}
My Cell:
import UIKit
import Firebase
class JobTableViewCell: UITableViewCell {
#IBOutlet weak var jobLabel: UILabel!
var job: Job! {
didSet {
jobLabel.text = job.text
}
}
}
Job.Swift:
import Foundation
import Firebase
class Job{
var text: String = ""
let ref: DatabaseReference!
init(text: String) {
self.text = text
ref = Database.database().reference().child("jobs").childByAutoId()
}
init(snapshot: DataSnapshot)
{
ref = snapshot.ref
if let value = snapshot.value as? [String : Any] {
text = value["text"] as! String
}
}
func save() {
ref.setValue(toDictionary())
}
func toDictionary() -> [String : Any]
{
return [
"text" : text,
]
}
}
And in my DestinationController:
import UIKit
import Firebase
class JobDetailViewController: UIViewController {
#IBOutlet weak var jobDetail: RoundText!
var valueToPass: String = ""
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
jobDetail.text = valueToPass
}
override func viewDidLoad() {
super.viewDidLoad()
title = "Jobinformation"
}
}
You should not be using cells to store data. You should have a data model that represents the data you are showing in the cells, and you should use the indexPath of the selected cell to look up the data in your data model.
Quick solution:
Change
performSegue(withIdentifier: "yourSegueIdentifer", sender: self) to performSegue(withIdentifier: "yourSegueIdentifer", sender: valueToPass)
2.Your prepare for Segue method should looks like this:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "yourSegueIdentifer" {
let destinationViewController = segue.destination as! AnotherViewController
destinationViewController.valueToPass = sender as? String
}
}
On AnotherViewController create var valuteToPass: String = "" and set your label.text = valueToPass
But I think you should not use currentCell.textLabel.text value, instead use the original value. (like if you set your currentCell as cell.textLabel.cell = array[indexPath.row], your valueToPass should be valueToPass = array[indexPath.row])
EDIT:
You use didDeselectRowAt method, instead of didSelectRowAt.
Change func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) to
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
Don't use global variable, create it in didSelectRowAt.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("You selected cell #\(indexPath.row)!")
// Get Cell Label
let indexPath = tableView.indexPathForSelectedRow!
let currentCell = tableView.cellForRow(at: indexPath)! as UITableViewCell
let valueToPass = currentCell.textLabel?.text
print("value: \(valuteToPass)")
performSegue(withIdentifier: "toDetails", sender: valueToPass)
}
On DestinationController:
class DestinationController: UIViewController {
var valuteToPass: String = ""
override func viewDidLoad() {
super.viewDidLoad()
jobLabel.text = valueToPass
}
}
EDIT2
JobTableViewController
delete var valueToPass:String!
Change let valueToPass = jobs[indexPath.row].text instead of let valueToPass = currentCell.textLabel?.text
I checked this change in your code, this will work.
Hi I cant comment but im using #Dris answer and I kept getting this error that says
Could not cast value of type 'UITableViewCell' (0x115464e18) to 'NSString' (0x10fa594c8).
The SIGABRT targets the line destinationViewController.valueToPass = (sender as? String)!
Why is that?
this is basically my code
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// Determine what to do when a cell in a particular section is selected.
print("did select: \(indexPath.row) ")
// Get Cell Label
let indexPath = tableView.indexPathForSelectedRow!
let currentCell = tableView.cellForRow(at: indexPath)! as UITableViewCell
valueToPass = currentCell.textLabel?.text
print("valueToPass: \(String(describing: valueToPass))")
performSegue(withIdentifier: "cellToView", sender: self)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell:UITableViewCell?
if tableView == self.tableView {
let currentNotif = notificationList[indexPath.row]
cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as UITableViewCell
cell?.textLabel?.text = currentNotif.notifType
cell?.detailTextLabel?.text = "\(currentNotif.notifTime) \n\(currentNotif.notifContent)"
}
if tableView == self.tableViewAnnounce {
let currentAnnounce = announcementList[indexPath.row]
cell = tableView.dequeueReusableCell(withIdentifier: "cellAnn", for: indexPath) as UITableViewCell
cell?.textLabel?.text = currentAnnounce.annouceType
cell?.detailTextLabel?.text = "\(currentAnnounce.annouceTime) \n\(currentAnnounce.annouceContent)"
}
return cell!
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "cellToView" {
// perform custom segue operation.
let destinationViewController = segue.destination as! ExtendedAnnouncementViewController
destinationViewController.valueToPass = (sender as? String)!
}
}
I'd avoid using a global variable to pass the data to the destination view controller. Defer the lookup until you are ready to pass the data.
And, avoid using force unwrap, it leads to runtime crashes.
Use something like this instead:
let segueIdentifier = "yourSegueIdentifer"
let labelDataSource: [String] = ["SomeText"]
func label(forIndexPath indexPath: IndexPath) -> String? {
let index = indexPath.row
guard labelDataSource.indices.contains(index) else { return nil }
return labelDataSource[index]
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: segueIdentifier, sender: indexPath)
}
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
guard segue.identifier == segueIdentifier,
let indexPath = sender as? IndexPath,
let destinationViewController = segue.destination as? AnotherViewController else { return }
destinationViewController.valuePassed = label(forIndexPath: indexPath)
}

How to handle tableview cell clicks and segues when delegate and datasource class is separate?

I tried to separate TableViewDelegate and TableViewDataSource to a separate class from ViewController and I'm facing a couple of problems now.
First problem:
App loads the tableview with all content but when I click on it or try to scroll all data disappears.
Second problem:
On click cell should link to another view where is more content displayed. I push data to this view using function. But when I separated the delegate and datasource to other class it doesnt work.
prepare(for segue: UIStoryboardSegue, sender: Any?)
Here is my code for view controller:
import UIKit
import Foundation
import os
class FirstViewController: UIViewController {
#IBOutlet weak var tableview: UITableView!
#IBOutlet weak var offlineModePicture: UIBarButtonItem!
#IBOutlet weak var refresh_button: UIBarButtonItem!
var wyznania_page = 0 // page
var isNewDataLoading = false
var wyznania = [[WyznanieData](),[WyznanieData](),[WyznanieData](),[WyznanieData](),[WyznanieData]()]
let activitiyViewController = ActivityViewController(message: "Ładowanie...😇")
override func viewDidLoad() {
super.viewDidLoad()
wyznania[wyznania_page].append(WyznanieData(date: "date", story: "story", sharedLink: "link", tag: "asd", fav: false, page: 1)!)
wyznania[wyznania_page].append(WyznanieData(date: "date", story: "story", sharedLink: "link", tag: "asd", fav: false, page: 1)!)
wyznania[wyznania_page].append(WyznanieData(date: "date", story: "story", sharedLink: "link", tag: "asd", fav: false, page: 1)!)
self.navigationController?.navigationBar.sizeToFit()
view.backgroundColor = UIColor.black
tabBarController?.tabBar.barTintColor = ColorsUI.bar
tabBarController?.tabBar.tintColor = UIColor.white
navigationController?.navigationBar.barTintColor = ColorsUI.bar
navigationController?.navigationBar.tintColor = UIColor.white
let customDelegate = TableViewDelegate(dataForRows: wyznania[wyznania_page])
self.tableview.delegate = customDelegate
self.tableview.dataSource = customDelegate
}
override internal var preferredStatusBarStyle : UIStatusBarStyle {
return .lightContent
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
switch(segue.identifier ?? "") {
case "ShowDetail":
guard let storyDetailViewController = segue.destination as? WyznanieViewController else {
fatalError("Unexpected destination: \(segue.destination)")
}
guard let selectedStopCell = sender as? Wyznanie else {
fatalError("Unexpected sender: \(String(describing: sender))")
}
guard let indexPath = tableview.indexPath(for: selectedStopCell) else {
fatalError("The selected cell is not being displayed by the table")
}
let selectedStory = wyznania[wyznania_page][(indexPath as NSIndexPath).row]
storyDetailViewController.wyznanie = selectedStory
default:
fatalError("Unexpected Segue Identifier; \(String(describing: segue.identifier))")
}
}
#IBAction func unwindToList(sender: UIStoryboardSegue) {
if let sourceViewController = sender.source as? WyznanieViewController, let story = sourceViewController.wyznanie {
if let selectedIndexPath = tableview.indexPathForSelectedRow {
// Update an existing story.
print("updating")
wyznania[wyznania_page][selectedIndexPath.row] = story
tableview.reloadRows(at: [selectedIndexPath], with: .none)
}
else {
// Add a new story
print("adding new")
}
}
}
}
And my Delegate and DataSource class:
[import UIKit
class TableViewDelegate: NSObject,UITableViewDelegate,UITableViewDataSource {
var wyznania = \[WyznanieData\]()
init(dataForRows: \[WyznanieData\]) {
self.wyznania = dataForRows
super.init()
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
}
func tableView(_ tableView: UITableView, didHighlightRowAt indexPath: IndexPath) {
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return wyznania.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "story_cell", for:indexPath) as? Wyznanie else {
fatalError("The dequeued cell is not an instance of WyznanieTableViewCell.")
}
let wyznanie = wyznania\[indexPath.row\]
cell.date.text = wyznanie.date
cell.story.text = wyznanie.story
cell.story.setContentOffset(CGPoint.zero, animated: true)
cell.story.textColor = UIColor.white
cell.backgroundColor = ColorsUI.cell_backgroung
cell.layer.borderWidth = 3.0
cell.layer.borderColor = ColorsUI.cell_borderColor
return cell
}
}]
1
[]
Try making your delegate variable global. it must be deallocation when goes out of scope in viewDidLoad (dataSource and delegate are weak in UITableView).
Extract following declaration global.
var customDelegate: TableViewDelegate!
then in viewDidLoad do following
customDelegate = TableViewDelegate(dataForRows: wyznania[wyznania_page])

Segue executing before DidSelectAtRow function

My situation consists of a UITable which when a row is selected it opens up a new VC and sends across a couple of variables.
My problem is that the segue is getting executed before the function DidSelectAtRow is run.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print ("hello")
self.selectedCellTitle = self.communities[indexPath.row]
self.cellId = indexPath.row
print ("now here: ", communityIds[cellId!])
self.performSegue(withIdentifier: "showCommunitySegue", sender: self)
}
I know this because the print commands above are not being executed. The app then crashes out when the next screen because the variables it expected to be there (cellId) are null.
If I delete the segue in storyboard and run then all the debug outputs in that function run correctly. As soon as I create the segue again the app switches to the new screen and crashes before any of that above code is run.
To create my segue I am:
1) right clicking on the cell in my UITableView on VC1 within Storyboard and dragging to my VC2
2) selecting type as show
3) copying the segue identifier name from the prepare for segue function in VC 1, and pasting it into the identifier attribute in Storyboard for the new segue.
Any ideas?
Below is the full code for VC1:
import UIKit
class CommunitiesViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var selectedCellTitle: String?
var cellId: Int?
var communities = [String]() //{ didSet { communitiesTableView.reloadData()
// }
// }
var communityIds = [String]()
var flag = true
var userEmailText: String?
var tempComId: Int?
#IBOutlet weak var joinCommunityButton: UIButton!
#IBOutlet weak var createCommunityButton: UIButton!
#IBOutlet weak var communitiesTableView: UITableView!
override func viewDidLoad() {
self.communitiesTableView.delegate = self
self.communitiesTableView.dataSource = self
super.viewDidLoad()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.communities.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath as IndexPath)
cell.textLabel?.text = self.communities[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print ("hello")
self.selectedCellTitle = self.communities[indexPath.row]
self.cellId = indexPath.row
print ("now here: ", communityIds[cellId!])
self.performSegue(withIdentifier: "showCommunitySegue", sender: self)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
if flag == true
{
self.communitiesTableView.reloadData()
let myUrl = URL(string: "http://www.quasisquest.uk/KeepScore/getDetails.php?");
var request = URLRequest(url:myUrl!);
request.httpMethod = "POST";
let postString = "email=\(self.userEmailText!)";
request.httpBody = postString.data(using: String.Encoding.utf8);
let task = URLSession.shared.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
DispatchQueue.main.async
{
if error != nil {
print("error=\(error)")
return
}
do{
let json = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [String:AnyObject]
if let arr = json?["communities"] as? [[String:String]] {
self.communities = arr.flatMap { $0["name"]!}
self.communitiesTableView.reloadData()
}
} catch{
print(error)
}
}
}
task.resume()
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
if segue.identifier == "createCommunitySegue" {
let createCommunityController: CreateNewCommunity = segue.destination as! CreateNewCommunity
createCommunityController.myEmail = self.userEmailText
}
if segue.identifier == "joinCommunitySegue" {
let joinCommunityController: JoinCommunity = segue.destination as! JoinCommunity
joinCommunityController.myEmail = self.userEmailText
}
if segue.identifier == "showCommunitySegue" {
let showCommunityController: ShowCommunityViewController = segue.destination as!ShowCommunityViewController
print("yes here: ", self.cellId!)
showCommunityController.communityIsCalled = self.selectedCellTitle
showCommunityController.comIds = self.communityIds
showCommunityController.communityId = self.cellId
}
}
}
You are creating your segue from Cell to ViewController, you need to create segue from ViewController to ViewController like this,

Resources