perform / trigger segue only after data is saved via parse - ios

I have a strange problem where the user inputs some data through a text box clicks ok, the IBAction function does this.
#IBAction func savedata(sender: AnyObject) {
var query = PFQuery(className:"xxxx")
....
....
parseObj.saveInBackgroundWithBlock({ (success: Bool, error: NSError?) -> Void in
if success {
println("Object Saved")
} else {
println("Error")
}
self.performSegueWithIdentifier("segueX", sender: self)
}
I want to make sure that this data is completely saved before the segue is performed. I tried having that function in prepareForSegue, but noted that the object is not saved till few secs after the next view controller is presented, as a result querying for the object in the next view controller viewdidload returns no results.
I also tried dispatch_async to save it, but without success. Not sure if this is a parse related question or iOS, but any suggestions would be helpful.
The solution is to add retries to the queries till results are returned in the target view controller, but I would like a much better solution for this.

Your code is missing a } and I am therefore unsure where the performSegue actually is placed. It should be placed in the callback - you might even want to move it in the success case to be able to do some error handling or resending or anything in the fail case:
#IBAction func savedata(sender: AnyObject) {
...
parseObj.saveInBackgroundWithBlock({ (success: Bool, error: NSError?) -> Void in
if success {
println("Object Saved")
self.performSegueWithIdentifier("segueX", sender: self)
} else {
println("Error")
// retry !? do something appropriate
}
}
}

You have the right idea, you want to save THEN perform the segue, however you're using the function incorrectly. According to the documentation: https://parse.com/docs/android/api/com/parse/ParseObject.html#saveInBackground(com.parse.SaveCallback). 'saveInBackground' is a asynchronous function, so you need to call 'performSegueWithIdentifier' from within the callback block to ensure the segue is done AFTER the object's been saved.

you can do it if you save in prepareForSegue then performSegue will be called

Related

How to wait for function to finish and update tableview before segue. iOS Swift

I am making an e-commerce app. On the basket view controller, when the user taps on the Checkout button, I'd like to check the availability of the items and update the basket accordingly when for example the stock is not enough, the quantity will be changed to available stock, or the product is removed from the basket when there's no stock available before segueing to another view controller. The problem is, I have a function that has a completion handler that is supposed to call the performSegue only when the function is finished and if there are no changes to the basket, however, it still goes to another view controller before the function is finished. Any advice on how to properly do this?
My completion handler was not done correctly that is why it was not working. But I finally got it to work with the following code
var changes = Bool()
func getBasketItems(completion: #escaping(_ success:Bool) -> Void) {
//each time there's an update to the database I set changes to true
changes = true
completion(changes)
}
I use it as the code below
self.getBasketItems { (success) in
if success {
// there are changes, update basket tableview
print("there are changes")
} else {
self.performSegue(withIdentifier: "goToMap", sender: self)
}
}

Segue is not working, and i can't figure out why

I have an if...then statement in the ViewDidLoad method for the view that acts as my storyboard entry point.
Basically, I am doing a check to see if there is any data in core data, to indicate that they've completed a small "setup form".
If it is found that the core data is empty or that the app has not been properly set up, I want it to automatically kick them over to the settings view with a segue.
My ViewDidLoad method looks like this:
override func viewDidLoad() {
super.viewDidLoad()
//Get any entries from the App Settings Entity
getAppSettings()
//If any entries are found, check to see if the setup has been completed
if (appSettings.count > 0) {
print("We found entries in the database for App Settings")
if (appSettings[0].setupComplete == false) {
print("Setup has not been completed")
self.performSegue(withIdentifier: "toAppSettings", sender: self)
} else {
print("Setup is completed")
//Load the settings into global variables
preferredRegion = appSettings[0].region!
usersName = appSettings[0].usersName!
}
} else {
print("We found no entries in the database for App Settings")
self.performSegue(withIdentifier: "toAppSettings", sender: self)
}
}
I made sure that the segue does exist, and that the identifier for the segue is exactly as I have it in the quotes (I even copied & pasted all instances of it to make sure that they are all consistent).
I also went the extra mile and put a checker in the "prepare for segue" method, to print whether it was getting called, and who the sender was:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
print("We're getting ready to segue!")
print(segue.identifier)
}
Both of those items get printed to the log, which tells me that the segue is being recognized and that the app is attempting to fire it. But - for some reason that I can't figure out - it simply isn't firing.
Any ideas what I am missing here?
I have an if...then statement in the ViewDidLoad
But that's the problem. viewDidLoad is way too early for this. Remember, all viewDidLoad means is that your view controller has a view. That's all it means. That view is not yet in the interface, and the view controller itself may not even be in the view hierarchy! It's just sitting out there in object space. You cannot segue from here; there is nothing to segue from.
Try waiting until viewDidAppear. If that makes it work, you might try moving it back to viewWillAppear, but I don't guarantee anything. Keep in mind that these methods can be called multiple times, so you might also need a flag to make sure that this segue fires just the once.

Swift 3: Checking Internet (Reocurring) viewDidAppear not

Ok, So I am rather new to Swift and I am a little confused about what I am trying to do, or if I am going in the wrong direction. (https://github.com/ashleymills/Reachability.swift)
Here is my viewDidLoad method :
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
checkConnection()
}
I then have a function with the code in from the Reachability GitHub Project:
func checkConnection() {
//declare this property where it won't go out of scope relative to your listener
let reachability = Reachability()!
reachability.whenReachable = { reachability in
// this is called on a background thread, but UI updates must
// be on the main thread, like this:
DispatchQueue.main.async {
if reachability.isReachableViaWiFi {
print("Reachable via WiFi")
} else {
print("Reachable via Cellular")
}
}
}
reachability.whenUnreachable = { reachability in
// this is called on a background thread, but UI updates must
// be on the main thread, like this:
DispatchQueue.main.async {
self.dim(direction: .In, alpha: self.dimLevel, speed: self.dimSpeed)
self.performSegue(withIdentifier: "mainToAlertNoInternet", sender: self)
}
}
do {
try reachability.startNotifier()
} catch {
print("Unable to start notifier")
}
}
As you can see, when there is no internet, this is the code:
self.dim(direction: .In, alpha: self.dimLevel, speed: self.dimSpeed)
self.performSegue(withIdentifier: "mainToAlertNoInternet", sender: self)
The Dim is taken from (http://www.totem.training/swift-ios-tips-tricks-tutorials-blog/ux-chops-dim-the-lights) the mainToAlertNoInternet loads the next view over the current one with transparence so it is an alert style.
So, the second segue has a view and a button on it. Nothing spectacular this is what is loaded when there is no internet:
That try again button is linked to the Exit of the Segue and runs this function in the First View Controller:
#IBAction func unwindFromSecondary(segue: UIStoryboardSegue) {
dim(direction: .Out, speed: dimSpeed)
checkInternetConnection()
}
I added in the function mainToAlertNoInternet so that when they click try again, it will go back to the first segue and run the test again. However, When I click try again, I get this error:
Warning: Attempt to present on whose view is not in the window hierarchy!
Hopefully I have explained enough what I have set up. Now to the Questions:
1) How can I fix this error?
2) Am I doing this the right way or is there a better way?
This is what I want:
I want to check the internet connection the moment the app loads. If there is no connection I want to display the segue like I have been doing. If the user clicks Try gain, I want it to go back to the first controller and run the check again and if still no connection display the segue like it did initially again. I would like this to be a repeating process until there is internet.
Appreciate all your help. Thanks in advance
Edit:
I have added the function call in the ViewDidAppear method like so:
override func viewDidAppear(_ animated: Bool) {
checkInternetConnection()
}
However, it does not run. Also the DIM in the unwindFromSecondary function does not get called when I do this.
Edit 2:
Just added this line into my viewDidAppear:
print("Appeared")
This gets called initially, but then not again.
Question:
How do I get a function to run once everything has loaded again after the unwindSegue?
Any thoughts?
Update 3
Ok, so I have looked at the answers below:
#MarkP Answer works fine. Thank you for that
However, the answer from #iSee has got me thinking maybe I should be going about this a different way.
I have added a Bounty to this post for a detailed answer that can show me and explain how to achieve the following:
In my app. I need to keep making sure that the internet exists (Maybe a Timer) on any view that loads. I would like it so that like the current way, If there is no internet it will pop up the ViewController with this segue:
performSegue(withIdentifier: "mainToAlertNoInernet", sender: self)
It appears that the App Delegate would be the place but I am unsure how to achieve this.
I am new to iOS Development and thus would appreciate some explanation and teaching.
Thank you for your time. It is greatly appreciated.
I presume you are new to iOS development in general.
The warning has nothing to do with your internet connectivity code.
The Warning you are getting is not an error. It is just that, a warning.
The reason you are getting that is well explained in this link and this
To get rid of that warning, you should not call performSegue from viewDidLoad(refer to the above link for more information.
To perform your network checks, it is advisable to use the AppDelegate (gives you a better control over the flow of the app)
All the best :)
EDIT: Please refer to this link here for more information on this. I could easily repost it here but since it is already answered, you can refer to the above link as to why it happens and how to avoid it.
As #iSee has pointed out with the links. This is because the view has not been added to the view hierarchy so you can't move to it. for this self.dismissViewControllerAnimated is needed:
#IBAction func unwindFromSecondary(segue: UIStoryboardSegue) {
dim(direction: .Out, speed: dimSpeed)
self.dismiss(animated: true) {
self.checkInternetConnection()
}
}
I use this in the app delegate ->
func reachablityCode() {
AFNetworkReachabilityManager.sharedManager()
AFNetworkReachabilityManager.sharedManager().startMonitoring()
AFNetworkReachabilityManager.sharedManager().setReachabilityStatusChangeBlock({(status) in
let defaults = NSUserDefaults.standardUserDefaults()
if status == .NotReachable {
defaults.setBool(false, forKey:REACHABLE_KEY)
}
else {
defaults.setBool(false, forKey: REACHABLE_KEY)
}
defaults.synchronize()
})
}
And then this in the base file ->
func isReachable() -> Bool {
return NSUserDefaults.standardUserDefaults().boolForKey(REACHABLE_KEY)
}

I can not call more than one application message in Swift

i need to send multiple text messages , raising the application message several times.
But the console show this error:
2016-08-27 19:27:17.237 AlertaTel 2.0[841:263754] Attempt to present <MFMessageComposeViewController: 0x15e19ba00> on <AlertaTel_2_0.ViewController: 0x15de43af0> which is waiting for a delayed presention of <MFMessageComposeViewController: 0x15e24ca00> to complete
I read on this site about this issue, but only found solutions or topics in Objective- c and honestly do not master the language even (I'm more oriented Swfit ).
I attached my codes:
Class MessageComposer
class MessageComposer: NSObject, MFMessageComposeViewControllerDelegate {
// A wrapper function to indicate whether or not a text message can be sent from the user's device
func canSendText() -> Bool {
return MFMessageComposeViewController.canSendText()
}
// Configures and returns a MFMessageComposeViewController instance
func configuredMessageComposeViewController(unicaVariable : String) -> MFMessageComposeViewController {
let messageComposeVC = MFMessageComposeViewController()
messageComposeVC.messageComposeDelegate = self // Make sure to set this property to self, so that the controller can be dismissed!
messageComposeVC.recipients = textMessageRecipients
messageComposeVC.body = "Estoy en peligro, aca esta mi última ubicación: https://maps.google.com/maps?q="+(view.locationManager.location?.coordinate.latitude.description)!+","+(view.locationManager.location?.coordinate.longitude.description)!+". "+(unicaVariable)
//view.performRequestAndUpdateUI()
return messageComposeVC
}
// MFMessageComposeViewControllerDelegate callback - dismisses the view controller when the user is finished with it
func messageComposeViewController(controller: MFMessageComposeViewController, didFinishWithResult result: MessageComposeResult) {
controller.dismissViewControllerAnimated(true, completion: nil)
}
}
In the ViewController:
func levantarMensaje(datoWebService: String){
if (messageComposer.canSendText()) {
let messageComposeVC = messageComposer.configuredMessageComposeViewController(datoWebService)
presentViewController(messageComposeVC, animated: true, completion: nil)
} else {
// Let the user know if his/her device isn't able to send text messages
}
}
And i call this method in a #IBAction:
#IBAction func sendTextMessageButtonTapped(sender: UIButton) {
levantarMensaje()
}
When I implemented a simple " FOR" on the IBAction the error that I showed above appears.
Thank you very much for your answers , greetings !
What's happening here is that you're trying to begin a modal presentation while the previous modal presentation is still animating. UIKit doesn't like that; you need to wait until one presentation finishes before starting the next one. There are a couple of ways to do this.
The first is to have several modal presentations at the same time, but to make sure the animations don't happen simultaneously. You could do this by changing your call to presentViewController(_:, animated:, completion:) to use the completion argument to present the next message view controller. That way the first message view would appear, and when it was finished animating the next one would begin, etc.
The other would be to wait until one message is sent (or cancelled) before presenting the next one. For that you'd replace controller.dismissViewControllerAnimated(true, completion: nil) with something similar to what I described above. Instead of passing nil for the completion argument, pass a closure that presents the next message view, until none remain.

Go back to previous controller from class swift

I have an app that use http calls to stream video from external storage.
When the user's device isn't connected to a network service, I need the app to go back to the previous controller.
the flow is the following: the user access a list of elements (table view cell), select one, then the app goes to the player controller. On this controller, the call is made to stream the file.
I use an api call handler in a class outside of the controller and I don't know how to proceed to make it go back to the previous controller from here (the element list).
Connectivity issues errors are all catched within the api class.
I don't show any code as Im not sure it would be relevant. If you need to see anything, let me know and I will update the question. What should be the way to do that? (of course I use a navigation controller)
Thanks
If you want go back to the previous view controller you should use:
navigationController?.popViewControllerAnimated(true)
If you need to use this function not in the view-controller but in another class you can use NSNotificationCenter for notify the view-controller when it's needed to show the previous controller, just like this:
YourViewController
override func viewDidLoad()
{
...
NSNotificationCenter.defaultCenter().addObserver(
self,
selector: "goBack:",
name: "goBackNotification",
object: nil)
...
}
func goBack(notification: NSNotification)
{
navigationController?.popViewControllerAnimated(true)
}
AnotherClass
NSNotificationCenter.defaultCenter().postNotificationName("goBackNotification", object: nil)
Don't forget to remove the observer in your YourViewController:
deinit
{
NSNotificationCenter.defaultCenter().removeObserver(self)
}
EDIT 1: you can use obviously a delegate instead of a NSNotification method. If you don't know the differences between NSNotification and delegate I recommend to you this answer.
A common approach besides NSNotificationCenter is to utilize closures or delegates to inform your ViewController that the streaming attempt failed. Using closures, the API of the class responsible for the streaming could be extended to take a completion closure as parameter, and call it with an NSError, if one occurred, or nil if it didn't.
func streamFile(completion: NSError? -> Void) {
// Try to start the streaming and call the closure with an error or nil
completion(errorOrNil)
}
When the call to the API in the ViewController is made you can then pass a closure to the method and check for errors. In case something went wrong an error should be present and the ViewController should be dismissed
func startStream() {
StreamingAPIClient.streamFile(completion: { [weak self] error in
if error != nil {
// Handle error
self.dismissViewControllerAnimated(true, completion: nil)
} else {
// Proceed with the streaming
}
})
}

Resources