Query for data to add data to prepareForSegue function - swift

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.

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 :).

Function creating 2 identical instances of class instead of only 1

I am relatively new to iOS development, any help will be greatly appreciated!
I'm trying to create a new instance of a class 'Event'.
class Event {
var EventName: String
var EventPhoto: UIImage?
init?(EventName: String, EventPhoto: UIImage?) {
guard !EventName.isEmpty else {
return nil
}
// Initial initilization of the values
self.EventName = EventName
self.EventPhoto = EventPhoto
// If some of the values are left blank, this will return nil to signal the problem
}
}
Below is the override function, which from my understanding is responsible for creating the instance:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
guard let button = sender as? UIBarButtonItem, button === saveButton else {
os_log("Cancelling Action, The Save Button Was not Pressed", log: OSLog.default,type: .debug)
return
}
let EventName = NewEventNameField.text ?? ""
let EventPhoto = NewEventImage.image
event = Event(EventName: EventName, EventPhoto: EventPhoto)
}
From my understanding, the override function should create a new instance of the class, which would then be displayed in a table view controller displaying a table of 'events'. My problem here is; when the function is called by the "create instance" button, it creates 2 identical instances with the same EventName and EventPhoto extracted from a textfield and an image in the view controller. In the tableview, there are basically 2 events that are exactly the same being displayed, which is what I am having trouble with since I don't see the code calling init twice anywhere, and can't figure out why the instances was created twice. After being created the 2 instances act independently and function like 2 separate instances would.
Thanks!
Thank You for the help, I found the issue in TableViewController's code file:
#IBAction func unwindToEventList(sender: UIStoryboardSegue){
if let sourceViewController = sender.source as?
NewEventViewController, let event = sourceViewController.event{
if let selectedIndexPath = tableView.indexPathForSelectedRow {
events[selectedIndexPath.row] = event
tableView.reloadRows(at: [selectedIndexPath], with: .none)
} else {
//Adding a new event instead of editing it.
let newIndexPath = IndexPath(row: events.count, section: 0)
events.append(event)
tableView.insertRows(at: [newIndexPath], with: .automatic)
}
//let newIndexPath = IndexPath(row: events.count, section: 0)
//events.append(event)
//tableView.insertRows(at: [newIndexPath], with: .automatic)
}
}
Turns out that I accidentally appended the instance to the list events and inserted it into the table an extra time outside the if statement.

Segue async Firebase data through NavigationController (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.

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

Making a prepareForSegue wait till after a Realm database write is completed

In my program when a button is pressed I am adding information to a database, including creating invoice number then calling a segue to a new view controller. When the new view controller is called I'd like to pass along that invoice number. Everything works fine, I can pass along sample data no problem. However, it appears that "override func prepareForSegue(segue: NSStoryboardSegue, sender: AnyObject!) {}" is being called before my button (upon initialization of the view controller?), so I am passing along a blank value. How can I make my prepareForSegue wait till after my button is pressed? Here is the code I currently have.
#IBAction func createInvoice(sender: AnyObject) {
let realm = Realm()
let invoicepull = Invoice()
let invoicecount = realm.objects(Invoice)
let invoicenraw = invoicecount.count
let a = 100
let invoicenumber = a + invoicenraw
var invoicefile = Invoice()
invoicefile.inumber = invoicenumber
invoicefile.cnumber = clientcombo.stringValue
invoicefile.cost = owed.doubleValue
invoicefile.paid = paid.doubleValue
invoicefile.sevicecode = service.stringValue
invoicefile.dateofservice = NSDate()
// Save your object
realm.beginWrite()
realm.add(invoicefile)
realm.commitWrite()
//Sent notification
performSegueWithIdentifier("cinvoiceseuge", sender: nil)
println("Inside Action")
println(invoicenumber)
dismissViewController(self)
}
override func prepareForSegue(segue: NSStoryboardSegue, sender: AnyObject!) {
if (segue.identifier == "cinvoiceseuge") {
//Checking identifier is crucial as there might be multiple
// segues attached to same view
var detailVC = segue.destinationController as! invociegenerator;
detailVC.toPass = invoicenumber
println("Inside Sugue")
println(invoicenumber)
}
}
Update: I belive this is an issue with the Realm database causing it to behave unexpectedly. If I remove all realm code, the program works as expected and I can pass a static dummy value.
invoicenumber in createInvoice() is a local variable and invoicenumber in prepareForSegue() seems to be an instance variable. is it what you expected?