Segue async Firebase data through NavigationController (Swift) - swift

I have been chasing this for two days but yet I am still not sure why my variable isn't being passed in my segue from my login view controller to the chat view controller via the navigation view controller.
I have a button that queries Firebase, checks if the user exists and returns a Firebase query reference for the user. I then want to pass this Firebase query reference when it finishes to the navigation controller's top view controller for use.
Inside my IBAction login button, I have:
var tempUserRef: FIRDatabaseReference?
channelRef.queryOrdered(byChild: "uid").queryEqual(toValue: uid).observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.exists() {
print("uid exist with \(snapshot.childrenCount) number of children")
for s in snapshot.children.allObjects as! [FIRDataSnapshot] {
tempUserRef = self.channelRef.child(s.key)
}
} else {
print("uid didn't exist")
if let name = self.nameField?.text { // 1
tempUserRef = self.channelRef.childByAutoId()
let channelItem = [
"name": name,
"uid": self.uid
]
tempUserRef?.setValue(channelItem)
}
}
self.userRef = tempUserRef
DispatchQueue.main.async {
self.performSegue(withIdentifier: "LoginToChat", sender: tempUserRef)
print("passsed \(self.userRef)")
}
})
Here is my segue:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if "LoginToChat" == segue.identifier {
if let navVc = segue.destination as? UINavigationController {
if let chatVc = navVc.topViewController as? ChatViewController {
chatVc.senderDisplayName = nameField?.text
if let userRef = sender as? FIRDatabaseReference {
chatVc.userRef = userRef
print("passsing \(self.userRef) to \(chatVc)")
}
}
}
}
super.prepare(for: segue, sender: sender)
}
The print statements all look good on my login controller but when I get to the chat view controller, the userRef is still nil. So my sense is that I have the right segue inputs and handoffs but that the async nature of the data is somehow out of step with my segue.
The chat view controller is using the JSQMessages library if that makes a difference.
Thanks for your help.
EDIT:
Based off feedback I've moved the super.prepare but userRef is still not being set consistently.
SECOND EDIT:
Following paulvs' suggestion, I removed my button segue. However, I did have to create another segue that connected view controller to view controller like this SO question.

Place the call to super.prepare at the end of the function. Otherwise, the segue is performed before you set your variables.

Related

fatal errors with optionals not making sense

I keep getting a fatal error saying how a value was unwrapped and it was nil and I don't understand how. When I instantiate a view controller with specific variables they all show up, but when I perform a segue to the exact VC, the values don't show up.
Take these functions for example...
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
if let displayVC = storyboard?.instantiateViewController(withIdentifier: Constants.Storyboards.TeachStoryboardID) as? SchoolEventDetailsViewController {
displayVC.selectedEventName = events[indexPath.row].eventName
displayVC.selectedEventDate = documentsDate[indexPath.row].eventDate
displayVC.selectedEventCost = documentsCost[indexPath.row].eventCost
displayVC.selectedEventGrade = documentsGrade[indexPath.row].eventGrade
displayVC.selectedEventDocID = documentsID[indexPath.row]?.docID
navigationController?.pushViewController(displayVC, animated: true)
}
}
This combined with this function :
func verifyInstantiation() {
if let dateToLoad = selectedEventDate {
dateEditableTextF.text = dateToLoad
}
if let costToLoad = selectedEventCost {
costEditableTextF.text = costToLoad
}
if let gradesToLoad = selectedEventGrade {
gradesEditableTextF.text = gradesToLoad
}
if let docIDtoLoad = selectedEventDocID {
docIDUneditableTextF.text = docIDtoLoad
}
if let eventNameToLoad = selectedEventName {
eventNameEditableTextF.text = eventNameToLoad
}
}
Helps load the data perfectly, but when I try to perform a segue from a search controller the data is not there.
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = selectedEventName
I set the title of the vc to have the event name , and I also recently added a text field to store it as well for experimental purposes (this question).
Now the issue is I want to do a data transfer from an Algolia Search Controller to that VC and I got all the other fields to show up, except for one and that was the document ID. So I created a completion handler function to get the document ID as a string and have it inserted into the vc when the segue is performed, just like how it's there when the vc is instantiated.
Here is the function :
func getTheEventDocID(completion: #escaping ((String?) -> ())) {
documentListener = db.collection(Constants.Firebase.schoolCollectionName).whereField("event_name", isEqualTo: selectedEventName ?? navigationItem.title).addSnapshotListener(includeMetadataChanges: true) { (querySnapshot, error) in
if let error = error {
print("There was an error fetching the documents: \(error)")
} else {
self.documentsID = querySnapshot!.documents.map { document in
return EventDocID(docID: (document.documentID) as! String)
}
let fixedID = "\(self.documentsID)"
let substrings = fixedID.dropFirst(22).dropLast(3)
let realString = String(substrings)
completion(realString)
}
}
}
I thought either selectedEventName or navigationItem.title would get the job done and provide the value when I used the function in the data transfer function which I will show now :
//MARK: - Data Transfer From Algolia Search to School Event Details
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
otherVC.getTheEventDocID { (eventdocid) in
if let id = eventdocid {
if segue.identifier == Constants.Segues.fromSearchToSchoolEventDetails {
let vc = segue.destination as! SchoolEventDetailsViewController
vc.selectedEventName = self.nameTheEvent
vc.selectedEventDate = self.dateTheEvent
vc.selectedEventCost = self.costTheEvent
vc.selectedEventGrade = self.gradeTheEvent
vc.selectedEventDocID = id
}
}
}
}
But it ends up showing nothing when a search result is clicked which is pretty upsetting, I can't understand why they're both empty values when I declared them in the SchoolEventDetailsVC. I tried to force unwrap selectedEventName and it crashes saying there's a nil value and I can't figure out why. There's actually a lot more to the question but I just tried to keep it short so people will actually attempt to read it and help since nobody ever reads the questions I post, so yeah thanks in advance.
I'm a litte confused what the otherVC is, which sets a property of itself in the getTheEventDocID, whilste in the closure you set the properties of self, which is a different controller. But never mind, I hope you know what you are doing.
Since getTheEventDocID runs asynchronously, the view will be loaded and displayed before the data is available. Therefore, viewDidLoad does not see the actual data, but something that soon will be outdated.
So, you need to inform the details view controller that new data is available, and refresh it's user interface. Something like
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
otherVC.getTheEventDocID { (eventdocid) in
if let id = eventdocid {
if segue.identifier == Constants.Segues.fromSearchToSchoolEventDetails {
let vc = segue.destination as! SchoolEventDetailsViewController
vc.selectedEventName = self.nameTheEvent
vc.selectedEventDate = self.dateTheEvent
vc.selectedEventCost = self.costTheEvent
vc.selectedEventGrade = self.gradeTheEvent
vc.selectedEventDocID = id
vc.updateUI()
}
}
}
}
and in the destination view controller:
class SchoolEventDetailsViewController ... {
override func viewDidLoad() {
super.viewDidLoad()
updateUI()
}
func updateUI () {
navigationItem.title = selectedEventName
// and so on
}
}
Ok so I decided to attempt a workaround and completely ditched the getTheEventDocID() method because it was just causing me stress. So I decided to ditch Firebase generated document IDS and just use 10 digit generated ids from a function I made. I also figured out how to add that exact same 10 digit id in the Algolia record by just storing the random 10 digit id in a variable and using it in both places. So now instead of using a query call to grab a Firebase generated document ID and have my app crash everytime I click a search result, I basically edited the Struct of the Algolia record and just added an eventDocID property that can be used with hits.hitSource(at: indexPath.row).eventDocID.
And now the same way I added the other fields to the vc by segue data transfer, I can now do the same thing with my document ID because everything is matching :).

Updating navigation in Viewcontrollers - Coredats

I am a new programmer using Swift.
In my project I am using coredata, and a lot of view controllers.
I have this view controllers:
Viewcontroller1 has my home Viewcontroller (VC1).
Viewcontroller2 (VC2), with a list of items reloaded from my coredata (in a table view).
Viewcontroller2 (VC3), lists the attributes of the selected item in VC2.
Viewcontroller2 (VC4), makes the user edit the attributes of selected item in VC2.
SO this is my navigation: VC1 -> VC2 -> VC3 -> VC4.
The problem:
Lets say I am in VC1 and go to VC2.
I now choose an item from view controller and it takes me to VC3 (I push VC3).
I am now in the item characteristics. which are listed from coredata.
To edit them I made a button, with a segue, VC4, where I made a view where the user can change the values of the choosen item. Once the user introduces any changes in the text fields, I do a NSFetchRequest, and update the values like this:
#IBAction func saveButton(_ sender: UIBarButtonItem) {
let app = UIApplication.shared.delegate as! AppDelegate
let context = app.persistentContainer.viewContext
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Simulator")
do {
let results = try context.fetch(request)
if results.count > 0 {
for result in results as! [NSManagedObject] {
result.setValue(designLabel.text, forKey: "designation")
result.setValue(typeLabel.text, forKey: "type")
result.setValue(localLabel.text, forKey: "local")
do {
try context.save()
} catch {
print("Error updating")
}
}
}
} catch {
print ("Error")
}
_ = self.navigationController?.popViewController(animated: true)
}
So now, by pressing saveButton I update local, designation and type atributes, and than pop VC3.
Now in VC3 I expected to receive the update values. Instead I am receiving the old values. This is what I have in VC3:
override func viewWillAppear(_ animated: Bool) {
super.viewDidAppear(animated)
map.delegate = self
let fetchRequest:NSFetchRequest<Simulator> = Simulator.fetchRequest()
do{
let searchResults = try DatabaseController.getContext().fetch(fetchRequest)
for result in searchResults as [Simulator]{
if (String(describing: result.objectID) == choosenID)
{
self.title = result.designation
localLabel.text = "Local: \(result.local!)"
typeLabel.text = "Type: \(result.type!)
print("I found it") //to check if I go inside this = and I go inside (true)
{
}
}
catch{
print("Error: \(error)")
}
}
}
Now I press the back button and I go to VC2: some thing. The cell, which have the designation on a label still don't updated it.
So I go to VC! using back button.
Now If I move forward to VC2 or VC3 everything Is updated.
What it's happening? Why isn't it updating when I pop from VC4 to VC3?
When you go back you are going back to the view as it was, when you go forward you are reloading it. What you can do though is reload the data when you go back.

Query for data to add data to prepareForSegue function

I am attempting to query for data from a Parse table to add it to a prepareForSegue function. But once I go into the newViewController the label is blank. Here's my line of code.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if(segue.identifier == "marathonDetail"){
var upcoming: marathonDetailViewController = segue.destinationViewController as! marathonDetailViewController
let indexPath = self.marathonsTableView.indexPathForSelectedRow!
let currentCell = marathonsTableView.cellForRowAtIndexPath(indexPath) as! marathonTableViewCell
let marathonEvents = currentCell.marathonName.text
upcoming.nameMarathon = marathonEvents
self.marathonsTableView.deselectRowAtIndexPath(indexPath, animated: true)
var query = PFQuery(className: "marathons")
query.whereKey("marathonName", equalTo: marathonEvents!)
query.findObjectsInBackgroundWithBlock{
(marathonPickeds: [PFObject]?, error: NSError?) -> Void in
if (error == nil){
if let marathonPicked = marathonPickeds as? [PFObject]?{
for marathonPicked in marathonPickeds!{
var selectedDescription = marathonPicked.description
upcoming.marathonDescription = selectedDescription
print(selectedDescription)
}
}
}else {
print(error)
}
}
}
}
The marathonsEvents= currentCell.marathonName.text works well but the marathonDescription is blank.
Any advice? I am using Parse as my backend XCODE 7, and swift
You're performing a network call on a background thread so by the time its finished you've already completed the segue. What you probably want to do is:
Pull that query logic out into a separate class, get it out of your view controllers.
In this instance perform the request in the view controller that is being pushed to. You can start it in viewWillAppear and refresh your view when its finished. It looks like it has all the information it needs to perform the request using just the marathonEvents.

CoreData and tableview

How can a tableview (in another view controller) read this data from first view controller?
Should I put this into array or something? This is code for retrieving saved data, I already managed to save data.
do {
let request = NSFetchRequest(entityName: "Users")
let results = try context.executeFetchRequest(request)
if results.count > 0 {
for item in results as! [NSManagedObject] {
let name = item.valueForKey("username")
let password = item.valueForKey("passwords")
print(name!, password!)
array.append (name, password) // this do not work, can't put that in array so tableview can read array in another vc.
}
}
}
When I put name and password objects in array it says:
Cannot convert any object to array string
How can I retrieve core data to array so tableview can read from array, or should it read from coredata directly?
If you want to share data from VC A to VC B, you should use segue method and the display data in the tableView.
Segue method
self.performSegue(withIdentifier: "identifier", sender: nil)
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "identifier" {
let destination = segue.destination as! AlreadyStartViewController
destination.variable = variable /*and other*/
}
}
And then display data in the tableView
If you want to ask me about code - ask

Segue not working

I am making an iOS app in swift where I have a tableView with cells, what I want is to transit to another view controller when I click on the cell. Here is my code block for the segue source.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "selectedEvent"{
let cellIndex = tableView.indexPathForSelectedRow();
let tempIndex = cellIndex?.row
let name = eventsManager.createdEvents[tempIndex!].name
let address = eventsManager.createdEvents[tempIndex!].address
let latD = eventsManager.createdEvents[tempIndex!].eventLat
let lonD = eventsManager.createdEvents[tempIndex!].eventLon
let coords = CLLocationCoordinate2DMake(latD, lonD)
}
println("segue fired")
}
Here is the code in the segue destination.
#IBAction func showOnMap(segue: UIStoryboardSegue){
println("segue check")
self.performSegueWithIdentifier("selectedEvent", sender: self)
let showEventController = segue.sourceViewController as EventListViewController
let focusAddress = showEventController.address
let position = showEventController.coords
let name = showEventController.name
var marker = GMSMarker(position: position!)
println("is marker working? I hope so")
marker.title = name
marker.map = self.mapView
}
The println statements are to check if the segue is firing and the first statement "segue fired" prints on the console, and the view in the simulator switches to the destination viewController. Unfortunately, the 2nd part of the code does not execute and I am having trouble figuring out why.
The println("segue check") line will only print if the code inside IBAction is triggered by an event. Make sure IBAction is connected to the proper object in your view controller's view.
Why are you calling self.performSegueWithIdentifier("selectedEvent", sender: self) at the destination view controller's showOnMap function and then appear to be getting an instance of the view controller where you came from?
If you want to save data in your segue destination, you should first define variables in your destination's view controller and then save it at the destination in your prepareForSegue above. Something like this:
if segue.identifier == "selectedEvent"{
...
let coords = CLLocationCoordinate2DMake(latD, lonD)
...
let myDestVC = segue.destinationViewController as MyDestinationViewController
let myDestVC.coords = coords
...
}
Once you've got all your data saved, you can use them where ever you want in your destination's view controller. If you want to use them immediately, you can do so by overriding viewDidLoad viewWillAppear or viewDidAppear,