Dropbox delegate methods are not calling first time - ios

This is i have written in viewDidLoad.
if DBSession.shared().isLinked() {
print("already linked")
initDropboxRestClient()
}
else
{
print("connecting2")
DBSession.shared().link(from: self)
initDropboxRestClient()
}
and function initDropboxRestClient() is written below.
func initDropboxRestClient() {
dbRestClient = DBRestClient(session: DBSession.shared())
dbRestClient.delegate = self
dbRestClient.loadMetadata("/")
}
The problem is i have two view controllers for displaying dropbox file names, the first view controller is calling the delegate methods and displaying filename and folder names perfectly. But the second one isn't.
In the second view controller,
I observed that if i scroll my tableview in second view controller up and down then the delegate methods get called immediately and once it is linked next time the methods are called immediately.
So for the first time delegate methods are not getting called in my second dropbox view controller thats my problem here. Thanks in advance.
The restClient delegate methods are as follows.
func restClient(_ client: DBRestClient!, loadedMetadata metadata: DBMetadata!) {
for file in metadata.contents
{
dbMetadataArray.append(file as! DBMetadata)
fileNamesArray.append((file as AnyObject).filename)
}
tableView.reloadData()
self.myActivityIndicator.stopAnimating()
self.myActivityIndicator.hidesWhenStopped = true
}
func restClient(_ client: DBRestClient!, loadMetadataFailedWithError error: Error!) {
print("in loadMetadataFailedWithError method in dropbox email view controller")
print("Error dscription = %#",[error.localizedDescription])
let alert = UIAlertController(title: "Go Back.", message: "Try Once Again", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil))
self.myActivityIndicator.stopAnimating()
self.myActivityIndicator.hidesWhenStopped = true
tableView.reloadData()
}

There are a few things that might cause your delegate methods to not be called:
Your rest client is nil or is being released (e.g., by ARC) prematurely.
You're making the call in a background thread that doesn't have a run loop.
Your delegate method that should be called back has a typo in it. Unfortunately the SDK doesn't warn you if it can't find a delegate method to call; it just completes without telling anyone.
Also, note that the SDK you're using uses API v1, which is deprecated and being retired soon anyway:
https://blogs.dropbox.com/developers/2016/06/api-v1-deprecated/
You should switch to API v2:
https://www.dropbox.com/developers/documentation

Related

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.

iOS: Which function to override when I need to do something after the view is visible to the user?

So the first thing my app does is get a list of movies from an API. I'm trying to handle if there's a network issue. Currently, in my viewDidLoad method, I call "updateApiInfo", which contains the following code:
if self.movies == nil {
print("stuff went wrong")
let alert = UIAlertController(title: "Network Error", message: "There was a nework error.\nYou must be connected to the internet to use Flicks.", preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Exit", style: UIAlertActionStyle.Default, handler: {(UIAlertAction) in
UIControl().sendAction(Selector("suspend"), to: UIApplication.sharedApplication(), forEvent: nil)
}))
self.presentViewController(alert, animated: true, completion: nil)
}
When viewDidLoad calls updateApiInfo, I get this message:
Warning: Attempt to present <UIAlertController: 0x7fad10e3ad80> on <Flicks.MoviesViewController: 0x7fad10e35cc0> whose view is not in the window hierarchy!
When I call updateApiInfo just from the user refreshing, the error pops up as expected.
I'm assuming that viewDidLoad only gets called before the view gets displayed to the user, which I guess is causing this error. Is there a method I can stick this code in for after the view is displayed to the user, so the problem can presumably get fixed?
You need to use viewDidAppear: to make sure the view is already in the window hierarchy.
There's a pretty good explanation of the order in which the methods are called here:
What is the process of a UIViewController birth (which method follows which)?

Swift UIAlertController file rename action

I am using Swift 2. This question relates to iOS9.
In brief:-
If the file rename button is clicked and the file name is still invalid then do I need to present the alert again or is there a smarter way of handling this?
In full:-
Saving a file imported from iCloud, I am presenting a UIAlertController called alertController if a file with the same name (.lastPathComponent) already exists in /Documents.
The UIAlertController has two actions titled Cancel and Rename and a .addTextFieldWithConfigurationHandler. If the file name already exists then the user is prompted to either cancel or rename.
This question relates to validating the new name and re-presenting (or not dismissing) the UIAlertController until the file name is valid:
If the user clicks the rename button and the file name is still the same, or is the same as another file which already exists, then I want the UIAlertController to be shown again. Or better still it would not be dismissed until the file name is valid.
The way I have done this (the only way I have figured out) is by adding a func named presentAlertController which presents the UIAlertController. This func is called from the rename handler when the button is clicked if the file name already exists (or has not been changed). (The same as simply presenting the UIAlertController again from the action).
My question:-
My code does what I want but can anyone suggest a neater and less clumsy way of achieving this outcome - without having to present the UIAlertController again?
Here is the relevant code. Please note that this whole section is within the completion handler of another function - hence the need for various references to self and why the UIAlertController code is within a dispatch_async(dispatch_get_main_queue()) (has to be called from the main queue).
//...
if checkFileExists(saveURL) { // saveURL: NSURL
dispatch_async(dispatch_get_main_queue()) {
let message = "File named `\(saveURL.lastPathComponent!)` already exists. Please import using a new name or else cancel."
let alertController = UIAlertController(title: "", message: message, preferredStyle: UIAlertControllerStyle.Alert)
alertController.addTextFieldWithConfigurationHandler { textField -> Void in
textField.text = saveURL.lastPathComponent! // it presents a text field containing the file name which needs to be changed
}
func presentAlertController() {
self.presentViewController(alertController, animated: true) {}
}
alertController.addAction(UIAlertAction(title: "Cancel", style: .Cancel, handler: nil))
alertController.addAction(UIAlertAction(title: "Rename", style: .Default) { action -> Void in
saveURL = saveURL.URLByDeletingLastPathComponent!.URLByAppendingPathComponent((alertController.textFields?.first!.text)!)
if checkFileExists(saveURL) {
presentAlertController() // currently it is calling a function to present the UIAlertController again if the file still exists when the button is clicked
} else {
saveXML(saveURL, dataObject: self.myThing)
self.fileTableView.reloadData()
}
})
presentAlertController() // this will be the first time that this called
}
}
//...
So you can add a target to your text field that will be called when the text field is edited, and in that function you can check if the user has typed a valid name. The problem you'll have with that is you'll need access to alertController so you can disable the "Rename" button. You can accomplish that by making a property at the top of your view controller like so:
var alertController: UIAlertController!
then revise the code your posted like so:
//...
if checkFileExists(saveURL) { // saveURL: NSURL
dispatch_async(dispatch_get_main_queue()) {
let message = "File named `\(saveURL.lastPathComponent!)` already exists. Please import using a new name or else cancel."
//just assigning here, not redeclaring (get rid of "let")
alertController = UIAlertController(title: "", message: message, preferredStyle: UIAlertControllerStyle.Alert)
alertController.addTextFieldWithConfigurationHandler { textField -> Void in
textField.text = saveURL.lastPathComponent! // it presents a text field containing the file name which needs to be changed
//Add the target here, calls on checkString
textField.addTarget(self, action: "checkString:", forControlEvents: UIControlEvents.EditingChanged)
}
func presentAlertController() {
self.presentViewController(alertController, animated: true) {}
}
alertController.addAction(UIAlertAction(title: "Cancel", style: .Cancel, handler: nil))
alertController.addAction(UIAlertAction(title: "Rename", style: .Default) { action -> Void in
saveURL = saveURL.URLByDeletingLastPathComponent!.URLByAppendingPathComponent((alertController.textFields?.first!.text)!)
if checkFileExists(saveURL) {
presentAlertController() // currently it is calling a function to present the UIAlertController again if the file still exists when the button is clicked
} else {
saveXML(saveURL, dataObject: self.myThing)
self.fileTableView.reloadData()
}
})
//Disable the "Rename" button to start, remove this line if you don't want that to happen
(alertController.actions as! [UIAlertAction])[1].enabled = false
presentAlertController() // this will be the first time that this called
}
}
//...
Then you'll have to make a checkString function (obviously you can rename this however you like as long as you also change the selector on the line where you add the target to the text field). Here's a little bit of code to give you an idea, but you'll have to write your own stuff here.
func checkString(sender: UITextField) {
//Pretty safe assumption you don't want an empty string as a name
if sender.text == "" {
(alertController.actions as! [UIAlertAction])[1].enabled = false
}
//As soon as the user types something valid, the "Rename" button gets enabled
else {
(alertController.actions as! [UIAlertAction])[1].enabled = true
}
}
This code has been tested, but not very rigorously, so comment if you have problems or if it works.

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

Commented out code still executes in Xcode

The code continually segues into the next view controller...even with performSegueWithIdentifier() commented out. It doesn't matter if the text fields are blank (which should prompt up an alert) or if the username/password is entered correctly, it just segues anyway.
I've tried Clean -> Build -> Run, as well as removing the view controller in Storyboard and re-adding a new one. What's the issue here?
PFUser.logInWithUsernameInBackground(userEmail, password: userPassword) {
(user: PFUser?, error: NSError?) -> Void in
//If user is found in Parse, log in
if user != nil {
var successAlert = UIAlertView(title: "Success", message: "Logged In", delegate: self, cancelButtonTitle: "OK")
successAlert.show()
//Transition to FacebookViewController
//self.performSegueWithIdentifier("loginToFacebookSegue", sender: self)
}
//Display warning - incorrect login
var warningAlert = UIAlertView(title: "Try again", message: "Username/password incorrect", delegate: self, cancelButtonTitle: "Dismiss")
warningAlert.show()
}
Your problem is that since your segue is attached to a UIButton, it is automatically called when the button is pressed. Your block is never really getting called. To fix this, drag your connection from view controller to view controller. Make sure you are zoomed out in the storyboard, then just create the connection (option dragging from a black spot in the view controller itself not the button). Make sure to give your segue an identifier.
Then call performSegueWithIdentifier when you need to run the segue.

Resources