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

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?

Related

Why Does My Object Not Transfer To The Next VC?

I am trying to transfer my object from HockeyDetailVC to my FavouritesVC using a button but my object is nil when I reach my second VC FavouritesVC. Why is it like that when I set the variable in my firstVC with my func transferObj()?
HockeyDetailVC
var item: CurrentPlayers?
override func viewDidLoad() {
super.viewDidLoad()
gonnaLoadView()
tableV.bounces = false
tableV.alwaysBounceVertical = false
favButton.layer.cornerRadius = 10
print(item) *//prints my current players object*
}
func transferObj() {
let otherVC = FavouritesVC()
otherVC.currentFav = item
print(item). *//prints my current player object*
}
#IBAction func addToFav(_ sender: Any) {
transferObj()
print("Favourite button Pressed")
}
FavouritesVC
var currentFav: CurrentPlayers?
override func viewDidLoad() {
super.viewDidLoad()
if currentFav == nil {
//display nil
self.tableView.separatorStyle = UITableViewCell.SeparatorStyle.none
print(favArr) *//prints empty array*
print(currentFav) *//nil*
} else {
favArr.append(currentFav!)
print(favArr)
}
}
As #Martin stated, let otherVC = FavouritesVC() creates a new instance of the controller, but it is not the instance that you will eventually display. So you are effectively setting the currentFav of a random FavouritesVC that will never actually be displayed, while the one you eventually do navigate to has it's currentFav property still unset.
To set the appropriate FavouritesVC instance, you need to access it in one of several ways (depending on how you present it). If it is through a segue, then you can reference it in the prepare(for segue: sender:) method. (When you create a Cocoa Touch Class file, the below method template is pre-populated. As it states, reference the new view controller using segue.destination.)
/*
// 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.
}
*/
Alternatively, if you create and present the new view controller programmatically with something like
// 1.
let otherVC = storyboard?.instantiateViewController(withIdentifier: "yourFavouritesVCIdentifier")
// 2.
// 3.
self.show(otherVC, sender: self)
you can insert your otherVC.currentFav = item at line // 2..

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

Userdefaults Boolean for button

I am fairly new to Swift programming. Using Userdefaults I was trying to customize user behaviour. Below image is of my initial controller. I require to save userdefaults so that App remembers the user selection of button, (i.e. A or B). Can you assist to provide me a function that I use in viewDidLoad and it remembers the button selection and segues to its respective ViewController.
My code to perfrom segue if Button A or B is selected is
let parent = self.storyboard?.instantiateViewController(withIdentifier: "DashboardVC") as! DashboardVC
self.navigationController?.pushViewController(parent!, animated: true)
Yet it doesnt segue. It keeps loading my initial viewcontroller.
do like
set the tag for each button and create the common method for handle the function , for e.g
#IBAction func handle_Action(_ sender: UIButton) {
defaultName.set(sender.tag, forKey: "yourKeyName")
}
and in your class
class ViewController: UIViewController {
let defaultName = UserDefaults.standard
// finally access the integer in your Viewload
override func viewDidLoad() {
super.viewDidLoad()
let getVal = defaultName.integer(forKey: "yourKeyName") as Int
if getVal == 1{ //called by A
}else if getVal == 2{
//called by B
}else{ // not interactwithButton action }
}

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.

Realm causing program to behave unexpectedly. (OS X and Swift)

So I am creating a fairly simple program using Realm as my database. I am fairly new to programing in Swift (or any OS X or iOS environment.) In my program when a button is pressed IBAction func createInvoice I want a few things to happen, I want to count the previous rows in the database and create an invoice number, I want to write new data to the database and I want to call a new view and view controller and pass along the invoice number. My code works except for one thing when using Realm the new view controller is called (override func prepareForSegue) before the invoice number is created so a 0 value is passed along to the new view controller.
If I create a dummy invoice number value such as let invoicenumber = 42 everything works perfectly. It seems that Realm is causing things to happen 'out of order' How can I make the veiwcontroller wait for a value before loading?
#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)
}
}
If createInvoice is happening on a different thread than prepareForSegue, you'll have to refresh the realm (Realm().refresh()) before accessing your invoicenumber variable (which I assume is of type RealmSwift.Object).
I have solved this issue, thanks to the help of #Shmidt by using Realm's built in notification center. To use the notifications you can use this basic structure.
var notificationToken: NotificationToken?
deinit{
let realm = Realm()
if let notificationToken = notificationToken{
realm.removeNotification(notificationToken)
}
}
override func viewDidLoad() {
super.viewDidLoad()
let realm = Realm()
notificationToken = realm.addNotificationBlock { note, realm in
println("The realm is complete")
}
...
}
One small other error in my code was let invoicenumber = a + invoicenraw I needed to drop the let as it is a variable and not a constant.