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

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()
}
}
}

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

a simple performSegue throws and error in prepare for segue func

Maybe something silly, but I am trying to simply move from a tableviewController to a viewController not passing any data just a simple move using
#IBAction func requestPanel(_ sender: Any) {
performSegue(withIdentifier: "sendMail", sender: AnyObject)
}
And then I get an error in a prepare for segue func.
But what does that have to no with my other segue?
I understand that that segue will have trouble since there is no data.
As you see here segue is above prepare for segue.
#IBAction func requestPanel(_ sender: Any) {
self.performSegue(withIdentifier: "sendMail", sender: AnyObject.self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
var indexPath: IndexPath = self.tableView.indexPathForSelectedRow!
let desti = segue.destination as! DeviceDetailTableViewController
let selectedRecord = onlineDevices[indexPath.row]
let panelWidth = selectedRecord.object(forKey: "panelwidth") as? String
let panelHight = selectedRecord.object(forKey: "panelhight") as? String
let panelPitch = selectedRecord.object(forKey: "panelpitch") as? String
let panelPower = selectedRecord.object(forKey: "panelpower") as? String
let panelWeight = selectedRecord.object(forKey: "panelweight") as? String
let panelMaker = selectedRecord.object(forKey: "maker") as? String
let panelModel = selectedRecord.object(forKey: "model") as? String
desti.pawidth = panelWidth!
desti.pahight = panelHight!
desti.papitch = panelPitch!
desti.papower = panelPower!
desti.weight = panelWeight!
desti.maker = panelMaker!
desti.model = panelModel!
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return onlineDevices.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "DeviceCell", for: indexPath)
// Configure the cell...
let noteRecord: CKRecord = onlineDevices[(indexPath as IndexPath).row]
cell.textLabel?.text = noteRecord.value(forKey: "maker") as? String
cell.detailTextLabel?.text = noteRecord.value(forKey: "model") as? String
return cell
}
}
All segues in a viewController will go through the same prepare(for:sender:) method. You need to use the segue.identifier to handle the different segues differently.
Your sendMail segue comes from a UIBarButton action instead of from a selected row, so check for that segue and handle it separately:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "sendMail" {
// nothing extra to be done here
} else {
// do your normal path here
if let indexPath = self.tableView.indexPathForSelectedRow {
// you have a valid selected row so proceed
}
}
}
The error message is clear. You are saying tableView.indexPathForSelectedRow at a time when no row is selected. Therefore that value is nil, and you crash when you force-unwrap it.
You wrote your original prepareForSegue when your only segue was triggered by the user selecting a row of the table. But this segue is not triggered by the user selecting a row of the table; it is triggered by the user tapping a button. You have to account for that difference.
(This is a major flaw in the whole architecture: all segues from a given view controller flow into the same prepareForSegue, which becomes a bottleneck.)

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.

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

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

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