present UIAlertController while a presentation is in progress - swift

I'm trying to display two different alerts immediately after each other. The first one is a loading indicator. The second one is an alert that tell the user if what they were doing has succeeded or not following a UDP message. I have simplified the code for presentation purposes.
My problem is that although I'm dismissing the loadingAlert before I call my second alert I still get an error:
Warning: Attempt to present UIAlertController on ViewController while a presentation is in progress!
My second alert is never shown.
Here is my Swift 4 code:
func getUpdate()
{
let loading = alert(title: "Performing Task...")
DispatchQueue.global(qos: .userInitiated).async {
let server:UDPServer=UDPServer(address:"0.0.0.0", port:5006)
let (data,_,_) = server.recv(1024)
DispatchQueue.main.async
{
loading.dismiss(animated: false, completion: nil)
OperationQueue.main.addOperation
{
alert(title: "How can I show this alert?")
}
}
}
}
func alert(title:String) -> UIAlertController
{
let alertController = UIAlertController(title: title, message: "", preferredStyle: UIAlertControllerStyle.alert)
...
rootViewController?.present(alertController, animated: true, completion: nil)
return alertController
}
What would be the best way of doing this?

Make use of the completion parameter in the call to dismiss:
DispatchQueue.main.async{
loading.dismiss(animated: false) {
OperationQueue.main.addOperation {
alert(title: "How can I show this alert?")
}
}
}

Related

Swift 3 How to display a confirmation screen based on MFMailComposeResult email screen

I'm building an app that constructs an email and presents it in a MFMailComposeViewController for the user to send. When the user sends or cancels it I want the app to respond with an appropriate confirmation screen message.
I'm able to compose the email, dismiss the compose screen, and I have a named segue in IB from the pre-compose view to the confirmation view. But I cannot get that segue to execute.
So, how can I update text message in the segue destination and then segue to it.
Because I'm trying to learn Swift I'm very interested in understanding how the code works, not just getting code that works. So I really do appreciate any help.
The workflow starts with the user snapping a photo from the app:
func snapPhoto(){
if let cameraConnection = sessionOutput.connection(withMediaType: AVMediaTypeVideo) {
sessionOutput.captureStillImageAsynchronously(from: cameraConnection, completionHandler: { buffer, error in
let myMessage = self.buildEmail()
let myJpg = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(buffer)
let mapSnap = (self.myMap == nil) ? nil : UIImagePNGRepresentation(self.myMap!)
let mail = self.setupMail(to: myMessage.to, subject: myMessage.subject, body: myMessage.body, myJpg: myJpg!, mapSnap: mapSnap)
self.presentMailComposer(mail: mail)
}) // close completionHandler
} // close if let cameraConnection
} // close func snapPhoto
Which assembles all of the email message content and passes it to:
func presentMailComposer(mail : MFMailComposeViewController) {
if MFMailComposeViewController.canSendMail() {
self.present(mail, animated: true, completion: nil)
} else {
let sendMailErrorAlert = UIAlertController.init(title: "Uh oh!", message: "Unable to send email.", preferredStyle: .alert)
self.present(sendMailErrorAlert, animated: true, completion: nil)
} // close if
} // close presentEmailComposer
And then this fires when the user taps "Send" of "Cancel"
public func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
switch result.rawValue {
case MFMailComposeResult.cancelled.rawValue:
self.performSegue(withIdentifier: "afterEmail", sender: self)
print("Mail cancelled")
case MFMailComposeResult.saved.rawValue:
print("Mail saved")
case MFMailComposeResult.sent.rawValue:
print("Mail sent")
case MFMailComposeResult.failed.rawValue:
print("Mail sent failure: %#", [error!.localizedDescription])
default:
break
}
controller.dismiss(animated: true, completion: nil)
} // close mailCompose
And this is where I find myself stumped. I can access MFMailComposeResult, and it is correct, but I cannot figure out how to present the confirmation view so it is available as the compose view slides away.
You need to make your view controller the MFMailComposeViewController delegate and override the method didFinishWith result and switch MFMailComposeResult value inside the completion handler of the dismiss method :
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
controller.dismiss(animated: true) {
// do whatever you need to do after dismissing the mail window
switch result {
case .cancelled: print("cancelled")
case .saved: print("saved")
case .sent:
let alert = UIAlertController(title: "Mail Composer", message: "Mail was successfully sent", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Done", style: .default, handler: nil))
self.present(alert, animated: true)
case .failed: print("failed")
}
}
}
If you want to present an alert before the controller is dismissed? Try this one:
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
switch result {
case .cancelled:
let alertController = UIAlertController.init(title: "Cancelled", message: "Some message", preferredStyle: .alert)
alertController.addAction(UIAlertAction.init(title: "Ok", style: .default, handler: { (alertAction) in
controller.dismiss(animated: true, completion: nil)
}))
controller.present(alertController, animated: true, completion: nil)
case .sent:
let alertController = UIAlertController.init(title: "Sent", message: "Some message", preferredStyle: .alert)
alertController.addAction(UIAlertAction.init(title: "Ok", style: .default, handler: { (alertAction) in
controller.dismiss(animated: true, completion: nil)
}))
controller.present(alertController, animated: true, completion: nil)
default:
break;
}
}

Does replaykit allow to record the screen and then broadcast the screen?

Im having a problem where after I broadcast my screen live, I then try to record the screen but it doesn't work. Same thing happens when I record my screen and then I try to broadcast the screen live. Im not trying to do them together btw. This is after one is done and I try to use the other one. Let me know if you need to see code or more info. Im in Swift 3 and using the new replay kit framework. Thanks!
EDIT: THIS IS THE CODE IM USING
//LIVE STREAM REPLAYKIT=====================================================================
func broadcastActivityViewController(_ broadcastAVC: RPBroadcastActivityViewController, didFinishWith broadcastController: RPBroadcastController?, error: Error?) {
print("=====hello delegate \(broadcastController?.broadcastURL) (error)")
self.broadcastController = broadcastController
self.broadcastController?.delegate = self
broadcastAVC.dismiss(animated: true) {
self.broadcastController?.startBroadcast(handler: { error in
print("start broadcast \(error)")
print("\(broadcastController?.broadcastExtensionBundleID)")
print("==url=\(broadcastController?.broadcastURL)")
print("==serviceInfo=\(broadcastController?.serviceInfo)")
//This is called when the broadcast is live
})
}
}
func broadcastController(_ broadcastController: RPBroadcastController, didFinishWithError error: Error?) {
print("broadcastController====delegate")
let alert = UIAlertController(title: "Alert", message: "There was an error broadcasting your screen. Please try again", preferredStyle: UIAlertControllerStyle.alert)
// show the alert
self.view!.window?.rootViewController!.present(alert, animated: true, completion: nil)
alert.addAction(UIAlertAction(title: "Okay", style: UIAlertActionStyle.destructive, handler: { action in
// add action
}))
}
func broadcastController(_ broadcastController: RPBroadcastController, didUpdateServiceInfo serviceInfo: [String : NSCoding & NSObjectProtocol]) {
print("broadcastController====didUpdateServiceInfo")
}
//LIVE STREAM REPLAYKIT=========================================================
//RECORD SCREEN REPLAYKIT-------------------------------------------------------------------
func startRecoding() {
let recorder = RPScreenRecorder.shared()
if recorder.isAvailable {
recorder.startRecording(handler: { (error) in
if error == nil { // Recording has started
} else {
// Handle error
print("Dont Allow Recording")
}
})
} else {
print("Did not record screen")
//if iphone or ipad doesnt support replaykit
// create the alert
let alert = UIAlertController(title: "Alert", message: "Please make sure your device supports ReplayKit!", preferredStyle: UIAlertControllerStyle.alert)
// show the alert
self.view!.window?.rootViewController!.present(alert, animated: true, completion: nil)
alert.addAction(UIAlertAction(title: "Try Again!", style: UIAlertActionStyle.destructive, handler: { action in
// add action
}))
}
}
func stopRecording() {
let sharedRecorder = RPScreenRecorder.shared()
sharedRecorder.stopRecording(handler: { (previewViewController: RPPreviewViewController?, error) in
if previewViewController != nil {
print("stopped recording")
previewViewController!.previewControllerDelegate = self
let alertController = UIAlertController(title: "Recording", message: "Tap view to watch, edit, share, or save your screen recording!", preferredStyle: .alert)
let viewAction = UIAlertAction(title: "View", style: .default, handler: { (action: UIAlertAction) -> Void in
self.view?.window?.rootViewController?.present(previewViewController!, animated: true, completion: nil)
})
alertController.addAction(viewAction)
self.previewViewController = previewViewController!
self.previewViewController.modalPresentationStyle = UIModalPresentationStyle.fullScreen
self.view?.window?.rootViewController!.present(alertController, animated: true, completion: nil)
}
else {
print("recording stopped working")
//create the alert================================
let alert = UIAlertController(title: "Alert", message: "Sorry, there was an error recording your screen. Please Try Again!", preferredStyle: UIAlertControllerStyle.alert)
// show the alert
self.view!.window?.rootViewController!.present(alert, animated: true, completion: nil)
alert.addAction(UIAlertAction(title: "Try Again!", style: UIAlertActionStyle.destructive, handler: { action in
// add action
}))
}
})
}
func previewControllerDidFinish(_ previewViewController: RPPreviewViewController) {
print("cancel and save button pressed")
previewViewController.dismiss(animated: true, completion: nil)
//dismiss preview view controller when save or cancel button pressed
}
I believe that this is a bug in ReplayKit, I'm not sure if it has been resolved as of 10.1 or not, but it is worth trying the 10.1 beta to see if it solves your issue.

Swift: UIAlertController not presented within dispatch_async

I have a setting where I used UIAlertController to show progress of a task till the task has some status to return.
The code looks like this
class AlertVCDemo: UIViewController {
let alertVC = UIAlertController(title: "Search",
message: "Searching....", preferredStyle:
UIAlertControllerStyle.Alert)
override func viewDidLoad() {
super.viewDidLoad()
alertVC.addAction(UIAlertAction(title: "Ok", style: .Default, handler: { action in
switch action.style{
case .Default:
print("default")
case .Cancel:
print("cancel")
case .Destructive:
print("destructive")
}
}))
}
func showAlertVC() {
dispatch_async(dispatch_get_main_queue(), {
self.presentViewController(self.alertVC, animated: true, completion: nil)
})
}
#IBAction func searchButtonClicked(sender: AnyObject) {
showAlertVC()
// Now do the real search that will take a while,
// depending on the result change the message of the alert VC
}
}
Somehow I see that the alert view controller is not shown in the main thread. The search logic completes eventually. I am using iOS 9.3. I did thorough research before asking this question and none of the solutions suggested on similar threads helped. Not sure why dispatch_async doesn't present the alert VC when search is still happening.
Even if I have the dispatch_async not in a method, things don't change.
#IBAction func searchButtonClicked(sender: AnyObject) {
showAlertVC()
// Now do the real search that will take a while,
// depending on the result change the message of the alert VC
}
is called in the main thread. So i guess your "search code" is blocking the UI thread.
Try something like this
#IBAction func searchButtonClicked(sender: AnyObject) {
showAlertVC()
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
// put your search code here
}
}
Check for UIViewController's presentedViewController property. It should be nil. If it is not nil, presentViewController method wont work.

How to show one UIAlertController after the other in Swift

I want to show user a message once.
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
let defaultsDatafirstTrue = NSUserDefaults.standardUserDefaults()
if let _ = defaultsDatafirstTrue.stringForKey("firstTrue") {
} else {
let alertController = UIAlertController(title: "You can add road markers just do long press on the Map", message: "", preferredStyle: .Alert)
let OKAction = UIAlertAction(title: "OK", style: .Default) { (action:UIAlertAction) in
}
alertController.addAction(OKAction)
self.presentViewController(alertController, animated: true, completion:nil)
defaultsDatafirstTrue.setObject("true", forKey: "firstTrue")
}
}
But I have error Warning: Attempt to present <UIAlertController: 0x7c2a8000> on <****: 0x7d193800> whose view is not in the window hierarchy! Because at the first run the iOS app displays a warning to the user that will use the location determination.
How can I see my message after the system message?
Run the code from viewDidAppear: not from viewWillAppear:.
The problem with viewWillAppear: is that it is called before the view is actually visible, so you cannot present another view from it yet. Also, viewWillAppear: can be actually called multiple times and then cancelled (e.g. for interactive transitions).

Why can't won't the MFMailComposerViewController be dismissed?

Whenever I press "Cancel" then "Delete Draft", the mail composer won't be dismissed. The error I'm getting is "Thread 1: EXC_BAD_ACCESS (code=1, address=0x40363380)"
In my TableViewController I have:
#IBAction func mailButton(sender: AnyObject) {
let emailComposer = EmailComposer()
if email != "" {
print(email)
if emailComposer.canSendMail() {
emailComposer.setRecipient(email)
let configuredMailComposeViewController = emailComposer.configuredMailComposeViewController()
presentViewController(configuredMailComposeViewController, animated: true, completion: nil)
}
} else {
let alertController = UIAlertController(title: "Sorry!", message: "No email found for this contact", preferredStyle: .Alert)
alertController.addAction(UIAlertAction(title: "OK", style: .Default, handler: { (action) -> Void in
//do nothing
}))
self.presentViewController(alertController, animated: true, completion:nil)
}
}
For those who don't know, EXC_BAD_ACCESS means its trying to access something in memory that is no longer there. I wrongfully created the EmailComposer() object after the button tap so it was going out of scope. So this:
let emailComposer = EmailComposer()
...should have been created here, for example:
class TableViewController: UITableViewController {
let emailComposer = EmailComposer()