This question already has answers here:
Passing Data between View Controllers in Swift
(7 answers)
Closed 5 years ago.
Objective: Pass an array of information from tableview to another tableview using the data created.
Problem: Couldn't access the array information from TableViewController in preparingForSegue
Result: TableViewController contains employee names, and when click on each of the employee, it goes to the details of showing arrays of information.
1) Employee.swift (model for data)
struct Employee {
var name: String
var food: [String]
var ingredients: [String]
init(name: String, food: [String], ingredients: [String]) {
self.name = name
self.food = food
self.ingredients = ingredients
}
}
2) TableViewController.swift (shows the name of the employee)
Everything is working well here with the codes, the only thing that I am struggling here is passing the information to the next viewcontroller. In the comment section is where I tried typing destination.food = lists[indexPath.row].food. This didn't work as expected. I am trying to retrieve the array information.
class VillageTableViewController: UITableViewController {
var lists : [Employee] = [
Employee(name: "Adam" , food: ["Fried Rice","Fried Noodles"], ingredients: ["Insert oil, cook","Insert oil, cook"])]
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showVillages" {
if let indexPath = self.tableView.indexPathsForSelectedRows {
let destination = segue.destination as? DetailsViewController
// pass array of information to the next controller
}
}
}
3) DetailsTableViewController.swift (shows an array of information)
Code is working fine. I have the idea of creating an empty array and the information will be passed from TableViewController. In the comment section, I will then write cell.textLabel?.text = food[indexPath.row] and cell.subtitle?.text = ingredients[indexPath.row]
class DetailsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var food:[String] = []
var ingredients:[String] = []
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! DetailsTableViewCell
//this is where I will get the information passed from the array
return cell
}
Please advise. I've been researching for the past few hours and couldn't figure it out yet. Bear in mind that all the code works fine here and I've only struggling in accessing the array information.
Edit: I am looking for arrays to be accessed and to be displayed on DetailsTableViewController.
According to your question you are going to pass one item (the employee) to the detail view controller, not an array.
Do that:
In VillageTableViewController replace prepare(for with
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showVillages",
let indexPath = self.tableView.indexPathForSelectedRow {
let employee = lists[indexPath.row]
let destination = segue.destination as! DetailsViewController
destination.employee = employee
}
}
In DetailsViewController create a property employee
var employee : Employee!
Now you can access all properties of the employee in the detail view controller, for example using its food array as data source
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return employee.food.count
}
and
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! DetailsTableViewCell
cell.textLabel.text = employee.food[indexPath.row]
return cell
}
PS: Rather than separate arrays for food (names) and ingredients I recommend to use a second struct (in this case you don't need to write any initializers in both structs)
struct Food {
let name : String
let ingredients: [String]
}
struct Employee {
let name: String
let food: [Food]
}
var lists : [Employee] = [
Employee(name: "Adam" , food: [Food(name: "Fried Rice", ingredients:["Insert oil, cook"]),
Food(name: "Fried Noodles", ingredients: ["Insert oil, cook"])])
The benefit is you can easily use sections in the detail view controller.
func numberOfSections(in tableView: UITableView) -> Int {
return return employee.food.count
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let food = employee.food[section]
return return food.name
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let food = employee.food[section]
return food.ingredients.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! DetailsTableViewCell
let food = employee.food[indexPath.section]
cell.textLabel.text = food.ingredients[indexPath.row]
return cell
}
Having a closer look self.tableView.indexPathsForSelectedRows is an array of IndexPaths. Why don't you try
destination.food = lists[indexPath[0].row].food
In prepare set food according to selected employee from lists
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showVillages" {
guard let indexPath = tableView.indexPathsForSelectedRow,
let destinationVC = segue.destination as? DetailsViewController else {
return
}
destinationVC.food = lists[indexPath.row].food
}
}
Related
I have been at this for a few days now, what I am trying to do is to be able to select 5 cells (previous populated by a realm database). Any help on this would be really appreciated.
Thanks Mark
The following is my current code as is relates to the Segue to pass the data. I think I have edited the code down to the key elements. Let me know if you need any other information.
Edits Made - based on the suggestion made below. I took a step back and approached the problem differently, and developed a solution. I have updated the code below to reflect my working solution. Hope this helps someone else in the future. Cheers Mark
Primary VC
import UIKit
import RealmSwift
class ExercisesSelectionTimed: UITableViewController{
var realm = try! Realm()
var exercises : Results<ExercisesModel>?
#IBOutlet weak var tableview: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
//populates the Realm Data with the default list
exercises = realm.objects(ExercisesModel.self)
tableView.allowsMultipleSelection = true
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return exercises?.count ?? 0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ExercisesAddedTableViewCell", for: indexPath) as! ExercisesAddedTableViewCell
// get the specific exercise in the array
let exercise = exercises?[indexPath.row]
cell.exercisenameLabel.text = exercise?.name
cell.equipmentnameLabel.text = exercise?.equipment
return cell
}
private func registerTableViewCells() {
let textFieldCell = UINib(nibName: "ExercisesAddedTableViewCell",bundle: nil)
self.tableview.register(textFieldCell,forCellReuseIdentifier: "ExercisesAddedTableViewCell")
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
switch (timerSelected) {
case "FGB":
//User needs to select 5 exercises to populate the FGB timer on the next VC
if let selectedRows = tableView.indexPathsForSelectedRows {
//lets the user select 5 cells
if selectedRows.count == 5 {
performSegue(withIdentifier: K.FGB, sender: self)
}
}
default : print ("error")
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
switch (timerSelected) {
case "FGB":
if let selectedRows = tableView.indexPathsForSelectedRows {
let selected = selectedRows.map{exercises?[$0.row]}
let destinationVC = segue.destination as! FBGTimerVCTable
destinationVC.selectedExercise = selected
}
default : print ("error")
}
}
}//last bracket in Class
The target for the data
import UIKit
import RealmSwift
class FBGTimerVCTable: HeartRateMonitorBrain{
var realm = try! Realm()
var selectedExercise = [ExercisesModel?]()
#IBOutlet weak var tableview: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
}
}//last bracket in Class
extension FBGTimerVCTable: UITableViewDataSource{
//setup tableview
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ExercisesAddedTableViewCell", for: indexPath) as! ExercisesAddedTableViewCell
//get the specific 5 exercises selected in the ExercisesSelectionTimed VC
cell.exercisenameLabel.text = selectedExercise[indexPath.row]?.name
cell.equipmentnameLabel.text = selectedExercise[indexPath.row]?.equipment
return cell
}
private func registerTableViewCells() {
let textFieldCell = UINib(nibName: "ExercisesAddedTableViewCell",bundle: nil)
self.tableview.register(textFieldCell,forCellReuseIdentifier: "ExercisesAddedTableViewCell")
}
}//last bracket in Class Extension
Realm
import RealmSwift
class ExercisesModel : Object {
#objc dynamic var name : String = ""
#objc dynamic var equipment : String = ""
}
I need to pass my array of ProductNames, to ProductSelectionViewController(VCB) From HomeViewController(VCA)
The issue is that it will only pass the first item. Ex:
If I tap on the cell called "ITEM 1" it passes only "Alk1" rather than "Alk1", "Alk2", "Alk3"
I used print statements and it is passing the Parameter correctly, I can't grasp the reason it will not pass the entire array.
NOTE: segue is from the cell in storyboard to the second VC
The Data Model:
class Parameter {
var parameterName: String
var productName: [String]
init(parameterName: String, productName: [String] = []) {
self.parameterName = parameterName
self.productName = productName
}
}
Home View Controller (VCA):
var parameters: [Parameter] = [
Parameter(parameterName: "Item1", productName: ["Alk1", "Alk2", "Alk3"]),
Parameter(parameterName: "Item2", productName: ["Cal1", "Cal2", "Cal3"]),
Parameter(parameterName: "Item3", productName: ["mag1", "mag3", "mag2"])
]
// MARK: - View life cycle
override func viewDidLoad() {
super.viewDidLoad()
prepareCollectionView()
}
// MARK: - UICollectionViewDataSource
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return parameters.count
}
#objc override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ParameterCell.identifier, for: indexPath) as? ParameterCell
else { preconditionFailure("Failed to load collection view cell") }
cell.parameter = parameters[indexPath.row]
cell.backgroundColor = colors[indexPath.row]
return cell
}
// MARK: - UICollectionView Delegates
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let productViewController = segue.destination as? ProductSelectionViewController else {
fatalError()
}
if segue.identifier == HomeViewController.productSegueIdentifier {
if let indexPaths = collectionView.indexPathsForSelectedItems {
let indexPath = indexPaths[0]
print("\(String(describing: indexPath))")
let productList = parameters[indexPath.row] as Parameter
productViewController.products = [productList]
}
}
}
The ProductSelectionViewController (VCB)
class ProductSelectionViewController: UITableViewController {
var products = [Parameter]()
override func viewDidLoad() {
super.viewDidLoad()
// label.text = products?.parameterName
print("The sent data is: \(products)")
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("Amount of Product \(products.count)")
return products.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
let productItem = products[indexPath.row].productName
cell.textLabel?.text = productItem![indexPath.row]
//let p = products.productNames
//ce/ll.textLabel?.text = p[indexPath.row]
return cell
}
I expect the table view to present an array of the items being sent. i.e if Item 1 is tapped, the table view should represent "Alk1, Alk2, Alk3" for a total of three cells.
I am only getting one cell for "Alk1"
in numberOfRowsInSection, I used a print statement to see how many object are counted with
print("There are \(products.count) items")
return output:
There are 1 items
There are 1 items
There are 1 items
Yes,
On your prepareForSegue you create an array with [productList] but product list is only one item, the one that got selected, not the names it has inside.
In your ProductSelectionViewController then use products.productName.count because thats the array you want and in cellForRow do let productItem = products[0].productName[indexPath.row] so you can get the correct one.
But you can improve your code way more if you pass the correct array of productNames instead of creating an array manually and inserting the one Parameter object instead of the productNames it contains
let productList = parameters[indexPath.row] as Parameter
let names: [String] = productList.productName
productViewController.products = names
I am currently trying to display data from my real-time database of Firebase in different tableviews.
In my first tableview I load my first level of my database structure (this already works).
Now I want to see what the user selects in the first tableview, and then display the next level of my database in a new TableView.
I have a function in my second TableViewController.swift file where to save in the selected row from the first TableView.
This way I want to save the next level from my database into an array so that this data will be displayed in my second tableview. When I then debug my new array, I also see the correct data in the new array. However, the data is not displayed in the second TableView.
I guess it's because the data is not 100% ready before the TableView loads.
Do you have a tip?
Firebase Structure:
-sports
-Bicycle
-BMX
-Bike 1
-img: „bike1.png“
-text: „bike 1“
-Bike 2
-img: „bike2.png“
-text: „bike 1“
-Car
-Audi
-Car 1
-img: „car1.png“
-text: „car 1“
-Car 2
-img: „car2.png“
-text: „car 2“
FirstTableViewController:
import UIKit
import Firebase
class FirstTableViewController: UITableViewController {
var categorie = [String]()
func loadData() {
var ref: DatabaseReference!
ref = Database.database().reference()
ref.child("sports").observeSingleEvent(of: .value, with: { snapshot in
if let sports = snapshot.value as? [String: Any] {
for (title, _) in sports {
self.categorie.append(title)
self.tableView.reloadData()
}
}
})
}
override func viewDidLoad() {
loadData()
super.viewDidLoad()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return categorie.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "sportCell")
cell?.textLabel?.text = categorie[indexPath.row]
return cell!
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// Set the segue's identifier in the Storyboard
performSegue(withIdentifier: "firstToSecond", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "firstToSecond" {
guard let destination = segue.destination as? SecondTableViewController,
let indexPath = tableView.indexPathForSelectedRow else { return }
destination.detailedValue = categorie[indexPath.row]
}
}
}
SecondTableViewController:
import UIKit
import Firebase
class SecondTableViewController: UITableViewController {
var detailedValue: String?
var secondArray = [String]()
func setIndex(value: String) {
loadData(index: value)
}
func loadData(index: String) {
var ref: DatabaseReference!
ref = Database.database().reference()
if (index != "") {
ref.child("sports").child(index).observeSingleEvent(of: .value, with: { snapshot in
if let sports = snapshot.value as? [String: Any] {
for (title, _) in sports {
self.secondArray.append(title)
self.tableView.reloadData()
}
}
})
}
}
override func viewDidLoad() {
super.viewDidLoad()
if let detailedValue = detailedValue {
loadData(index: detailedValue)
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return secondArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "sorteCell")
cell?.textLabel?.text = secondArray[indexPath.row]
return cell!
}
}
UPDATE 1:
Thanks to #Jay Lee for the above code.
UPDATE 2:
You are not loading the data to the SecondTableViewController instance that is presented on your screen, but to a new SecondTableViewController instance that you created in the func tableView(_:=,cellForRowAt:) method in your FirstTableViewController.
The logs are printed from the multiple instances you created from it.
This is not what you want, as you are creating multiple SecondTableViewController instances every time a new cell shows in your FirstTableViewController.
You should rather get a reference to the actual SecondTableViewController that is presented and supply the data it.
If you are using a storyboard, you can use prepare(for:sender:) to do that.
We have two choices: provide the entire data from the FirstTableViewController to SecondTableViewController using a delegate design pattern, or just provide value to SecondTableViewController and leave the fetching to it.
Based on your code, you can just supply the SecondTableViewController with value that your setIndex(value:) method in the SecondTableViewController uses, and get the data after the SecondTableViewController loads.
For example, in your SecondTableViewController:
class SecondTableViewController: UITableViewController {
...
var detailedValue: String?
override func viewDidLoad() {
super.viewDidLoad()
if let detailedValue = detailedValue {
setIndex(value: detailedValue)
}
}
...
}
and in your FirstTableViewController:
class FirstTableViewController: UITableViewController {
...
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// Set the segue's identifier in the Storyboard
performSegue(withIdentifier: "yourIdentifier", sender: self)
}
...
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "yourIdentifier" {
guard let destination = segue.destination as? SecondTableViewController,
let indexPath = tableView.indexPathForSelectedRow else { return }
destination.detailedValue = categories[indexPath.row]
}
}
}
But note that you already have a data to be shown on SecondTableViewController in your FirstTableViewController, so you should probably make a protocol and set FirstTableViewController its delegate.
EDIT:
Your segue should not be connected like this:
but like this:
I have a view controller which is used as a table view. From the table view I wish to select an item from the rows available and once selected I wish for that chosen thing to be the name of a label in a second view controller.
So
tableViewController - select an item from the list in the table
secondViewcontroller - label name is what is selected in the tableViewController
I have looked around and there is talk of using NSNotification but I can't seem to get it to work, in addition to using prepareForSegue.
My relevant code for the tableViewContrller is this:
//MARK Table View
//number of sections the table will have
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
//number of rows each section of the table will have
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return peripheralArray.count
}
//the way the data will be displayed in each row for the sections
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
let BluetoothNaming = peripheralArray[indexPath.row].peripheral.name
cell.textLabel?.text = BluetoothNaming
return cell
}
//what happens when we select an item from the bluetooth list
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
stopScanning()
peripheral = peripheralArray[(indexPath as NSIndexPath).row].peripheral
print ("connecting to peripheral called \(peripheral)")
//store the name of the connected peripeheral
let connectedPeripheral = peripheral
manager?.connect(connectedPeripheral!, options: nil)
performSegue(withIdentifier: "segueBackwards", sender: nil)
}
My secondViewController does not have much on its just the label:
import UIKit
class HomepageViewController: UIViewController {
#IBOutlet var ConnectionLabel: UILabel!
func changeBlueoothLabel() {
self.ConnectionLabel.text = "aaaa"
}
}
What do I need to do so that when I select a row in the table that the label in the secondViewController changes its label to reflect it.
Furthermore, if I wanted to change it so that I have another label on the secondViewController and I wanted to change it from disconnected to connected, would there be much more work involved?
Many thanks
to pass data add a string variable in the HomepageViewController and implement prepareForSegue method to pass selected name from tableViewController to HomepageViewController
in HomepageViewController
var selectedName: String?
in tableViewController:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showHomePage" {
if let indexPath = self.tableView.indexPathForSelectedRow {
let peripheral = peripheralArray[indexPath.row].peripheral.name
let homeController = segue.destination as! HomepageViewController
homeController.selectedName = peripheral
}
}
in HomepageViewController
import UIKit
class HomepageViewController: UIViewController {
#IBOutlet var ConnectionLabel: UILabel!
var selectedName: String?
override func viewDidLoad() {
super.viewDidLoad()
// Used the text from the First View Controller to set the label
if let name = selectedName {
ConnectionLabel.text = name
}
}
}
I am trying to pass data from my Cheats struct into a new tableviewcontroller to populate the new table with the relevant info.
I have done research but am new to swift.
I do have experience in php but transferring my knowledge is proving quite difficult...
In my prepare for segue class i receive a few errors:
Definition conflicts with previous value:
var DataPass = CheatsArray[indexPath.row]
Cannot assign value type 'String' to '[String]':
DestViewController.CheatsArray = DataPass.name
Here is a copy of my current 3 files
Structs and arrays:
struct Game {
var name : String
var cheats : [Cheat]
}
struct Cheat {
var name : String
var code : String
var description : String
}
// Create Our Game Info And Cheats / Codes For Each Game!
//-------------------------------------------------------
let COD4 = Game(name: "Call Of Duty 4", cheats: [Cheat(name: "Cheat", code: "Code", description: "Description")])
let GTAV = Game(name: "Grand Theft Auto 5", cheats: [Cheat(name: "Cheat", code: "Code", description: "Description")])
// Place Our New Games Inside This Array!
//---------------------------------------
let ArrayOfGames = [COD4,GTAV]
GameListController:
import Foundation
import UIKit
class GamesListViewController: UITableViewController {
var CheatsArray = [Game]()
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return ArrayOfGames.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
cell.textLabel?.text = ArrayOfGames[indexPath.row].name
return cell
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let indexPath : NSIndexPath = self.tableView.indexPathForSelectedRow!
let DestViewController = segue.destinationViewController as! CheatsListViewController
let DataPass : Game
var DataPass = CheatsArray[indexPath.row]
DestViewController.CheatsArray = DataPass.name
}
}
CheatListController:
class CheatsListViewController: UITableViewController {
var CheatsArray = [String]()
override func viewDidLoad() {
super.viewDidLoad()
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return CheatsArray.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("cell2", forIndexPath: indexPath)
cell.textLabel?.text = CheatsArray[indexPath.row]
return cell
}
}
The first error is because you defined DataPass twice, one row after another. You can't do that in the same scope. Change the name of one.
The second error is because you cannot assign value type 'String' to '[String]'. Just looking at CheatsArray and name variables, it's clear the first is an array and the second is most likely a string. You might want to append the name to the array.