I can not call more than one application message in Swift - ios

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.

Related

calling popToRootViewController on the main thread after network callback is not working

When a user sends its register form, I am trying to popToRootViewController who is a login screen. To achieve this, I have a delegation callback when server side response is success. The problem is that if I call the popToRoot... method during the delegation callback, the current viewController is not poped.
RegisterView->RegisterPresenter->NetworkManager
NetworkManager->PresenterInput->RegisterView->RegisterPresenter->RegisterWireframe(call popToRootViewController over RegisterView)
On my Wireframe:
extension RegisterRouter: RegisterRouterProtocol {
func presentLoginBack(from: RegisterViewProtocol) {
if let vc = from as? UIViewController {
DispatchQueue.main.async() {
vc.navigationController?.popToRootViewController(animated: true)
}
}
}
}
On networkLayer:
guard (200...207) ~= status else {
if status == 210 {
self.presenterInputDelegate?.notifyEndRegisterSuccess()
}
As vpoltave mentioned, there was a warning:
popToViewController:transition: called on <UINavigationController 0x126844c00> while an existing transition or presentation is occurring; the navigation stack will not be updated.
This one made me aware that I was presenting an alert in the main thread as the same time that I was performing the popToViewController transition.
So, the solution was to carry out the popToViewController when the user dismiss the alert.

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

Presenting View Controller loses subviews when dismissing presented VC

I'm having some trouble playing around with two viewcontrollers that interact in a straightforward manner:
The homeViewController shows a to-do list, with an addTask button.
The addTask button will launch an additional viewController that acts as a "form" for the user to fill.
However, upon calling
self.dismissViewControllerAnimated(true, completion: nil);
inside the presented view controller I return to my home page, but it's blank white and it seems nothing can be seen except the highest-level view on the storyboard can be seen (i.e. the one that covers the entire screen).
All of my views, scenes, etc. were set up with autolayout in storyboard. I've looked around on Stack Overflow, which lead to me playing around with the auto-resizing subview parameter i.e.:
self.view.autoresizesSubviews = false;
to no avail. I'm either fixing the auto-resizing parameter wrong (in the wrong view of interest, or simply setting it wrong), or having some other problem.
Thanks in advance
edit:
I present the VC as follows:
func initAddNewTaskController(){
let addNewTaskVC = self.storyboard?.instantiateViewControllerWithIdentifier("AddNewTaskViewController") as! AddNewTaskViewController;
self.presentViewController(addNewTaskVC, animated: true, completion: nil);
}
edit2:
While I accept that using delegates or unwinding segue can indeed circumvent the problem I'm encountering (as campbell_souped suggests), I still don't understand what's fundamentally happening when I dismiss my view controller that causes a blank screen.
I understand that calling dismissViewControllerAnimated is passed onto the presenting view controller (in this case my homeViewController). Since I don't need to do any pre or post-dismissal configurations, the use of a delegate is (in my opinion) unnecessary here.
My current thought is that for some reason, when I invoke
dismissViewControllerAnimated(true, completion:nil);
in my addNewTaskViewController, it is actually releasing my homeViewController. I'm hoping someone can enlighten me regarding what it is exactly that I'm not understanding about how view controllers are presented/dismissed.
In a situation like this, I usually take one of two routes. Either set up a delegate on AddNewTaskViewController, or use an unwind segue.
With the delegate approach, set up a protocol:
protocol AddNewTaskViewControllerDelegate {
func didDismissNewTaskViewControllerWithSuccess(success: Bool)
}
Add an optional property that represents the delegate in your AddNewTaskViewController
var delegate: AddNewTaskViewControllerDelegate?
Then invoke the didDismissNewTaskViewControllerWithSuccess whenever you are about to dismiss AddNewTaskViewController:
If the record was added successfully:
self.delegate?.didDismissNewTaskViewControllerWithSuccess(true)
self.dismissViewControllerAnimated(true, completion: nil);
Or if there was a cancelation/ failure:
self.delegate?.didDismissNewTaskViewControllerWithSuccess(false)
self.dismissViewControllerAnimated(true, completion: nil);
Finally, set yourself as the delegate, modifying your previous snippet:
func initAddNewTaskController(){
let addNewTaskVC = self.storyboard?.instantiateViewControllerWithIdentifier("AddNewTaskViewController") as! AddNewTaskViewController;
self.presentViewController(addNewTaskVC, animated: true, completion: nil);
}
to this:
func initAddNewTaskController() {
guard let addNewTaskVC = self.storyboard?.instantiateViewControllerWithIdentifier("AddNewTaskViewController") as AddNewTaskViewController else { return }
addNewTaskVC.delegate = self
self.presentViewController(addNewTaskVC, animated: true, completion: nil);
}
...
}
// MARK: AddNewTaskViewControllerDelegate
extension homeViewController: AddNewTaskViewControllerDelegate {
func didDismissNewTaskViewControllerWithSuccess(success: Bool) {
if success {
self.tableView.reloadData()
}
}
}
[ Where the extension is outside of your homeViewController class ]
With the unwind segue approach, take a look at this Ray Wenderlich example:
http://www.raywenderlich.com/113394/storyboards-tutorial-in-ios-9-part-2
This approach involves Ctrl-dragging from your IBAction to the exit object above the view controller and then picking the correct action name from the popup menu

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

perform / trigger segue only after data is saved via parse

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

Resources