Swift 3: Pass String from TableViewController to another TableViewController [duplicate] - ios

This question already has answers here:
Passing data between view controllers
(45 answers)
Closed 6 years ago.
I have a UITableViewController showing all my users from Firebase in a list. If you tap on one user you see another UITableViewController with a static TableView Layout prepared in the Interface Builder to edit the user properties. I want to pass the UID of the selected user to the DetailTableViewController to load all of the current user data there.
EDIT: This question is not an exact duplicate. I want to pass data from one UITableViewController to another UITableViewController not a normal Detail UIViewController!
This is my current code of the first TableViewController.
Can somebody help me? I don't get it.
UserListTableViewController.swift:
import UIKit
import Firebase
import FirebaseAuth
import FirebaseDatabase
class UserListTableViewController: UITableViewController {
var dbRef:FIRDatabaseReference!
var user = [User]()
var writeSelectedUID:String!
var selectedUID: String = "Mister X"
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.startObservingDB()
}
func startObservingDB () {
dbRef.observe(.value, with: { (snapshot:FIRDataSnapshot) in
var newUser = [User]()
for user in snapshot.children {
let userObject = User(snapshot: user as! FIRDataSnapshot)
newUser.append(userObject)
}
self.user = newUser
self.tableView.reloadData()
}) { (error:Error) in
print(error.localizedDescription)
}
}
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return user.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "User Cell", for: indexPath) as! CustomUserListTableViewCell
// Configure the cell
let userRow = user[indexPath.row]
cell.userFirstLastNameLabel?.text = "\(userRow.firstName!) \(userRow.lastName!)"
cell.userUsernameLabel?.text = "#\(userRow.username!)"
}
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//let selectedUID = user[indexPath.row]
let selectedUserRow = user[indexPath.row]
self.writeSelectedUID = "\(selectedUserRow)"
performSegue(withIdentifier: "editUser", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destinationViewController.
let viewcontroller = segue.destination as! ManageUserSettingsTableViewController
// Pass the selected object to the new view controller.
if(segue.identifier == "editUser") {
viewcontroller.usernameTextField.text! = "\(self.writeSelectedUID)"
print("Var: \(self.writeSelectedUID)")
}
}
}

In UserListTableViewController.swift :
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destinationViewController.
let viewcontroller = segue.destination as! ManageUserSettingsTableViewController
// Pass the selected object to the new view controller.
//let object = self.writeSelectedUID as? String
// let object = self.writeSelectedUID as! String!
if(segue.identifier == "editUser") {
if let object = self.writeSelectedUID {
viewcontroller.detailItem = object as AnyObject?
}
}
}
In your AnotherViewController :
var detailItem: AnyObject?
usernameTextField.text = detailItem?.description

Related

Incorrect cell information being pushed to next view controller

So currently I have a table view:
When I click on a cell, occasionally, it will give incorrect info about the event. For example, here is when I add an extra event
When I click on this new event, it gives the wrong info, and instead gives the info for the "testing" event. Each name in the cell represents one event, which has info such as registered . users, time of event, ect. When I click on asfe, the info given is the one corresponding with testing. This bug happens everyonce in a while and i dont know what is causing it.
import UIKit
import Firebase
class AdminEvents{
var eventName:String?
var eventDesc:String?
var eventStartDate:String?
var eventEndDate:String?
var numPeople:Int?
var numRegister:Int?
init(evName:String, evDesc:String, evStartDate:String, evEndDate:String, evNumPeople:Int, evNumRegistered:Int){
self.eventName = evName
self.eventDesc = evDesc
self.eventStartDate = evStartDate
self.eventEndDate = evEndDate
self.numPeople = evNumPeople
self.numRegister = evNumRegistered
}
}
class AdminsEventsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var eventsArray = [String]()
var actualEvents:[AdminEvents] = []
#IBOutlet weak var tblEvents: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let id = Auth.auth().currentUser?.uid
Database.database().reference().child("users").child(id!).child("createdEvents").observe(.value) { snapshot in
//self.eventsArray.removeAll()
print("start")
print(snapshot.childrenCount)
let enumerator = snapshot.children
while let rest = enumerator.nextObject() as? DataSnapshot {
self.eventsArray.append(rest.key as! String)
}
Database.database().reference().child("Events").observe(.value) { (data) in
let events = data.value as! [String:[String:Any]]
for(_,value) in events{
print(self.eventsArray)
if(self.eventsArray.contains(value["EventName"]! as! String)){
self.actualEvents.append(AdminEvents(evName: value["EventName"]! as! String, evDesc: value["EventDescription"]! as! String, evStartDate: value["start time"]! as! String, evEndDate: value["end time"] as! String, evNumPeople: value["NumberOfPeople"]! as! Int, evNumRegistered: value["currentPeople"] as! Int))
}
}
print("Actual events array " + "\(self.actualEvents)")
}
self.tblEvents.reloadData()
}
self.tblEvents.dataSource = self
self.tblEvents.delegate = self
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("count: " + "\(eventsArray.count)")
return eventsArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "productstable", for: indexPath)
cell.textLabel?.text = self.eventsArray[indexPath.row]
return cell
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? RegisteredUsersViewController {
destination.events = actualEvents[(tblEvents.indexPathForSelectedRow?.row)!]
tblEvents.deselectRow(at: tblEvents.indexPathForSelectedRow!, animated: true)
}
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
}
You can see that in the prepare method, I am pushing the event name to the next view controller.
This the firebase structure
Edit: Someone was asking what is being pushed in the segue, and I forgot to add the class that has this.
class AdminEvents{
var eventName:String?
var eventDesc:String?
var eventStartDate:String?
var eventEndDate:String?
var numPeople:Int?
var numRegister:Int?
init(evName:String, evDesc:String, evStartDate:String, evEndDate:String, evNumPeople:Int, evNumRegistered:Int){
self.eventName = evName
self.eventDesc = evDesc
self.eventStartDate = evStartDate
self.eventEndDate = evEndDate
self.numPeople = evNumPeople
self.numRegister = evNumRegistered
}
}
In your prepare(for:sender:) function you are assuming that the table row selection value is the cell that triggered the segue, but this is not guaranteed.
It is safer to use the sender parameter to identify the row that was tapped:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? RegisteredUsersViewController,
let cell = sender as? UITableViewCell,
let indexPath = tblEvents.indexPath(for:cell) {
destination.events = actualEvents[indexPath.row]
tblEvents.deselectRow(at: indexPath, animated: true)
}
}
}

Swift Tableview Load Data from Firebase Database

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:

Accessing an Object (SQLite table) from another ViewController Swift 4

I am new to swift and what I am trying to do is return the specified columns in my table to display in a separate viewcontroller. I have defined a function using SQLite.swift which returns them in an array, just like I want them to be. (It works when called in the same viewcontroller)
func returncolumns() -> Array<String> {
print("RETURNING")
var namearray = [String]()
do {
for wardrobe in try wardrobedb.prepare(wardrobe.select(name)) {
namearray.append(wardrobe[name])
}
} catch {
print("This no worko \(error)")
}
return namearray
}
But when I call the function from another viewcontroller, I get a fatal error due to it trying to unwrap a nil value.
var clothes = ViewController().returncolumns()
What I gather is that since I am calling the function from the viewcontroller without the table, it is not able to get the information that it needs.
Here is the database in the first viewcontroller
class ViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
var wardrobedb: Connection!
let wardrobe = Table("wardrobe")
let id = Expression<Int64>("id")
let name = Expression<String>("name")
let type = Expression<String>("type")
let color = Expression<String>("color")
let style = Expression<String>("style")
let weight = Expression<String>("weight")
let pattern = Expression<String>("pattern")
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
do {
let documentDirectory = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
let fileUrl = documentDirectory.appendingPathComponent("wardrobe").appendingPathExtension("sqlite3")
let wardrobedb = try Connection(fileUrl.path)
// let wardrobedb = try remove(fileUrl.path)
self.wardrobedb = wardrobedb
} catch {
print(error)
}
}
So should I try to combine the view controllers so that its all accessible? or move the wardrobe instantiation to another class? or is there a nice simple addition I can make to the function call which will allow for the table to be accessed.
Thank you!
Second ViewController:
import UIKit
class WardrobeTableViewController: UITableViewController {
var clothes = [String]()
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return clothes.count
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return "Section \(section)"
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "LabelCell", for: indexPath)
let clothesname = clothes[indexPath.row]
cell.textLabel?.text = clothes[indexPath.row]
cell.detailTextLabel?.text = "Very cool!"
// cell.imageView?.image = UIImage(named: clothesname)
return cell
}
}
Here is first viewcontroller calls
func returncolumns() -> Array<String> {
print("RETURNING")
var namearray = [String]()
do {
for wardrobe in try wardrobedb.prepare(wardrobe.select(name)) {
namearray.append(wardrobe[name])
}
} catch {
print("This no worko \(error)")
}
return namearray
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "wardrobemove" {
// if let destinationVC = segue.destination as? WardrobeTableViewController {
WardrobeTableViewController().clothes = returncolumns()
/// }
}
}
So here I am identifying "wardrobemove" as the segue which moves into a navigation controller. I commented out the if statement because in this instance the destination viewcontroller is the navigation controller not the WardrobeTableViewController where the clothes array is being stored. When I run the debugger, I see it passing the information and the clothes array being assigned. but it still disappears before that.
Between the Navigation controller and the WardrobeTableViewController there is a Table view controller which has a few cells and when the first one is selected, it opens the WardrobeTableViewController.
This is a bit of a mess, I have followed two guides and I think the second one bringing in the navigation controller has messed things up a bit.
EDIT 2: you were not setting the clothes property of the instance of the class the segue is performed (but you were setting a new instance). Change like this:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
//check if the performed segue has "wardrobemove" id
if segue.identifier == "wardrobemove" {
//check if the destination is kind of WardrobeTableViewController
if let destinationVC = segue.destination as? WardrobeTableViewController {
//if it is so, then set its property clothes
destinationVC.clothes = returncolumns()
}
}
}
EDIT
In your second vc, you want to reload your tableView every time you set the array of clothes:
var clothes = [String]() {
didSet {
self.tableView.reloadData()
}
}
Old answer
var clothes = ViewController().returncolumns()
You have everything in your FirstVC and you want to set clothes in the SecondVC based on the value returned by the method returncolumns()
If it is so, you might want to perform a segue between FirstVC and SecondVC, and implement prepareForSegue to set clothes. Something like:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "segueIdentifier" {
if let destinationVC = segue.destination as? SecondViewController {
destinationVC.clothes = returncolumns()
}
}
}

viewDidLoad() not running after segue

I'm performing a segue from one table view to another.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
NSLog("You selected cell number: \(indexPath.row)!")
self.performSegue(withIdentifier: "types", sender: productList[indexPath.row])
}
It should run the viewDidLoad() of the new TableView described by the custom class of the ViewController (Which I've declared in Storyboard)
func viewDidLoad(parent: String) {
print("This should print")
super.viewDidLoad()
//self.typeTableView.delegate = self
//self.typeTableView.dataSource = self
//Set reference
ref = Database.database().reference()
//Retrieve posts
handle = ref?.child(parent).observe(.childAdded, with: { (snapshot) in
let product = snapshot.key as? String
if let actualProduct = product
{
self.productList.append(actualProduct)
self.typeTableView.reloadData()
}
})
}
Any Idea why this might be happening?
Embed navigation controller to your destination controller
and make a segue from current table views cell to it with identifier types.
Then add below method after your
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "types" {
if let indexPath = tableView.indexPathForSelectedRow {
let object = productList[indexPath.row] as! yourProductType
let controller = (segue.destination as! UINavigationController).topViewController as! YourDestinationViewController
controller.yourProductProperty = object
}
}
}
Make sure to declare yourProductProperty in your Destination controller so that you can access current product object in it.
viewDidLoad has no parameters and needs an override clause:
override func viewDidLoad() {
...
}
Your method signature is
func viewDidLoad(parent: String) {
but it should be
override func viewDidLoad() {
super.viewDidLoad()
// Your code
}
are you using same class to tableviewcell ? if yes then keep different identifier [RESUE IDENTIFIER] for both tableview.

Passing CoreData keys to new ViewController

I am working on one part of my app which will function as a journal for the user, in which they can add an entry, then review or delete them afterwards. I followed a few Swift tutorials for CoreData and I have confirmed that the CoreData setup is working. However, I am having trouble getting values to pass to the UIViewController that will display the journal after already being saved. In one configuration I had tried it displayed no data, in another the data would stay the same no matter what entry selected, and in the setup below, this line journalEntryViewer.journalViewerTextView.text = journalTextToPass returns "found nil when unwrapping optional value."
Here is the code for the UITableViewController that acts as the list:
import UIKit
import CoreData
class JournalCollectionViewer: UITableViewController {
var JournalEntry = [NSManagedObject]()
var journalTextToPass:String!
var journalTitleToPass:String!
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(true)
self.tableView.reloadData()
let appDelegate =
UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext
let fetchRequest = NSFetchRequest(entityName: "JournalEntry")
do {
let results =
try managedContext.executeFetchRequest(fetchRequest)
JournalEntry = results as! [NSManagedObject]
} catch let error as NSError {
print("Could not fetch \(error), \(error.userInfo)")
}
}
// MARK: - Table view data source
#IBOutlet weak internal var journalCollectionTable: UITableView!
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return JournalEntry.count
}
override func tableView(tableView: UITableView,
cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell =
tableView.dequeueReusableCellWithIdentifier("JournalCell")
let entry = JournalEntry[indexPath.row]
cell!.textLabel!.text =
entry.valueForKey("journalDate") as? String
return cell!
}
func selectedTableCell(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let entry = self.JournalEntry[indexPath.row]
self.performSegueWithIdentifier("ShowEntryViewer", sender: entry)
}
// MARK: - Navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "ShowEntryViewer" {
let journalEntryViewer = segue.destinationViewController as! JournalEntryViewer
journalEntryViewer.self.navigationItem.title = journalTitleToPass
journalEntryViewer.journalViewerTextView.text = journalTextToPass
}
}
}
In the Swift file for the viewer (journalEntryViewer), all I have done is declared the class and the UITextView. Needless to say, I am at a loss. Any help appreciated!
When you assign value to the IBOutlet object in the prepareForSegue method that time it is not initialized, so you need to pass the string object and assign that string object to the journalViewerTextView in the viewDidLoadof JournalEntryViewer.
First declare two instance var in the JournalEntryViewer like this
var journalTitle: String?
var journalText: String?
Now use this var in viewDidload
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.title = journalTitle
self.journalViewerTextView.text = journalTitle
}
Now pass this journalTitle and journalText in the prepareForSegue method of JournalCollectionViewer
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "ShowEntryViewer" {
let journalEntryViewer = segue.destinationViewController as! JournalEntryViewer
journalEntryViewer.journalTitle = journalTitleToPass
journalEntryViewer.journalText = journalTextToPass
}
}
New Edit:
Problem with your code is you have not used delegate method didSelectRowAtIndexPath instead of you are using something else selectedTableCell that is wrong try to implement delegate method of UITableView
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let entry = self.JournalEntry[indexPath.row]
self.performSegueWithIdentifier("ShowEntryViewer", sender: entry)
}
Now you are passing entry object so change your prepareForSegue like this
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "ShowEntryViewer" {
let journalEntryViewer = segue.destinationViewController as! JournalEntryViewer
let entry = sender as! NSManagedObject
journalEntryViewer.journalTitle = entry.valueForKey("journalDate") as? String
journalEntryViewer.journalText = entry.valueForKey("journalEntryText") as? String
}
}

Resources