I make call on button click. My code:
if let phoneCallURL:NSURL = NSURL(string: "tel://\(phoneNumber)") {
let application:UIApplication = UIApplication.shared
if (application.canOpenURL(phoneCallURL as URL)) {
application.openURL(phoneCallURL as URL);
}
else {
let alertController = UIAlertController(title: NSLocalizedString("CALL_C_TITLE", comment: "Call disallowed"), message: NSLocalizedString("CALL_C_DESC", comment: "This device cannot call"), preferredStyle: UIAlertControllerStyle.actionSheet)
alertController.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: "OK"), style: UIAlertActionStyle.default, handler: nil))
self.present(alertController, animated: true, completion: nil)
}
}
This code work fine, but i want to run some code after user end call. How I can do this?
When the call is ended and your application regains focus, it will receive the UIApplicationDidBecomeActive (docs) notification. You can register your ViewController as an observer for this and respond accordingly.
Keep in mind that you will receive this notification for other event sequences too (returning from background, dismissing notifications, etc.), so you may want to set a flag when starting the call and check for that flag in the notification handler if you only want to run code after having made the call.
Related
I'm new to Swift programming, but can't find an answer to my problem, which is...
When I present a simple UIAlertController with a UIAlertAction handler, I am expecting the alert to display until the user responds, then the handler is executed, before continuing with the remaining code.
Unexpectedly, it seems to finish off the code block before displaying the alert and executing the handler.
I've searched Stackoverflow, and re-read the Apple Developer Documentation for UIAlertController and UIAlertAction, but I can't figure out why the code doesn't pause until the user responds.
I've tried putting the UIAlertController code in its own function, but the alert still appears to be displaying out of sequence. I'm thinking maybe there needs to be a delay to allow the Alert to draw before the next line of code executes(?).
#IBAction func buttonTapped(_ sender: Any) {
let alert = UIAlertController(title: "Ouch", message: "You didn't have to press me so hard!", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Sorry", style: .default, handler: { _ in
self.handleAlert()
}))
self.present(alert, animated: true, completion: nil)
print("Should be printed last!")
}
func handleAlert() {
print("UIAlertAction handler printed me")
}
In the code above I am expecting the debug console to display:
UIAlertAction handler printed me
Should be printed last!
But instead it displays:
Should be printed last!
UIAlertAction handler printed me
Instead of adding a seperate function, can you put it within the alert action itself like this...
let alert = UIAlertController(title: "Ouch", message: "You didn't have to press me so hard!", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Sorry", style: .default, handler: { action in
// code for action goes here
}))
self.present(alert, animated: true)
UIAlertController is designed to run asynchronously (that is why it has you pass a block of code to execute when the action is performed instead of giving a return value)
So to fix your code, put the code you want to run after an action is chosen in another function, then call that function at the end of each UIAlertAction handler.
private var currentlyShowingAlert = false
#IBAction func buttonTapped(_ sender: Any) {
if currentlyShowingAlert {
return
}
let alert = UIAlertController(title: "Ouch", message: "You didn't have to press me so hard!", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Sorry", style: .default, handler: { _ in
self.handleAlert()
self.alertCleanup()
}))
self.present(alert, animated: true, completion: nil)
currentlyShowingAlert = true
}
func handleAlert() {
print("UIAlertAction handler printed me")
}
func alertCleanup() {
print("Should be printed last!")
currentlyShowingAlert = false
}
Be careful when doing things like pushing view controllers (or anything where the calls will stack up) in direct response to a button press.
When the main thread is busy, the button can be pressed multiple times before the first buttonTapped call happens, in that case buttonTapped could be called many times in a row, currentlyShowingAlert will prevent that issue.
Struggling with the following code:
var connected = false
while !connected {
let msg = "Press cancel"
let alert = UIAlertController(title: "Test", message: msg, preferredStyle: .alert)
let action = UIAlertAction(title: "Cancel", style: .default) { (action:UIAlertAction) in
connected = true
}
alert.addAction(action)
print ("Hello")
present(alert, animated: true, completion: nil)
}
The UIAlertController is never shown, "Hello" is printed over and over again
If I insert "connected = true" after the while clause then the UIAlertController is shown, but I'm not able to show it again by changing the action to "connected = false"
What am I doing wrong?
First of all, as mentioned in the comments, its not a good idea to present your alert controller continuously in a while loop. I believe your intended functionality is to display an alert whenever the connected variable becomes false.
To accomplish this use NotificationCenter to respond as follows:
In viewDidLoad:
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.displayAlert), name: NSNotification.Name(rawValue: "connectionDropped"), object: nil)
Add a willSet property observer to connected:
var connected: Bool! {
willSet {
if newValue == false && oldValue != false {
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "connectionDropped"), object: nil)
}
}
}
Then whenever you set self.connected = false, you will run this method:
#objc func displayAlert() {
let msg = "Press cancel"
let alert = UIAlertController(title: "Test", message: msg, preferredStyle: .alert)
let action = UIAlertAction(title: "Cancel", style: .default) { (action:UIAlertAction) in
self.connected = true
}
alert.addAction(action)
print ("Hello")
present(alert, animated: true, completion: nil)
}
Just make sure you set connected after the view hierarchy has been loaded e.g in viewDidAppear.
Once you're done with the view you can then remove the observer:
deinit {
NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: "connectionDropped"), object: nil)
}
EDIT:
The functionality you need is provided with the Reachability framework, in particular with the reachabilityChanged notification. You can then call the displayAlert using a similar approach that I outlined above; this is documented on their README document.
The UIAlertController is never shown, "Hello" is printed over and over again
It is inappropriate to keep presenting the alert in an endless while loop, probably, it leads to facing "AlertController is not in the window hierarchy" issue.
If I insert "connected = true" after the while clause then the UIAlertController is shown, but I'm not able to show it again by
changing the action to "connected = false"
Of course you won't be able to show it again by default, the chunk of code is already been executed.
Solution:
Presenting the alert should be related to triggering a specific condition, not in a while loop.
For instance, if you are aiming to notify the user that the device is unconnected, You could use Reachability and observe reachabilityChanged to present the alert, for more details you could check the library - Example - notifications section.
I managed to solve it using a recursive call to the function:
func showDlg(){
ssid = getWiFiSsid() ?? "AAA"
ssid = ssid[ssid.startIndex..<ssid.index(ssid.startIndex, offsetBy: 2)]
if ssid != "GP" {
print ("SSID: " + ssid)
let msg = "\nNot connected to GoPro camera\n\nPlease connect to camera wireless network and revert\n "
alert = UIAlertController(title: "GoPro Golfswing Analyser", message: msg, preferredStyle: .alert)
let action = UIAlertAction(title: "Try again", style: .default) { (action:UIAlertAction) in
print("Testing network again")
self.showDlg()
}
alert.addAction(action)
let action1 = UIAlertAction(title: "Cancel", style: .default) { (action:UIAlertAction) in
print("Cancel - exiting")
}
alert.addAction(action1)
present(alert, animated: true, completion: nil)
} else{
print("Connected to GoPro")
}
}
Alright, I've looked up a lot of documentation on how the UIAlertController's work and I have a rather unique scenario. My application is being designed to work with a HID Bluetooth scanner. When I use:
preferredStyle: UIAlertControllerStyle.Alert
After I generate an alert that the item I have scanned is incorrect. Cool, alert is happening, problem is if I scan again ( which emulates keyboard input ), the return key is being sent to the alert and the alert is running the dismiss action.
If I use:
preferredStyle: UIAlertControllerStyle.ActionSheet
Then the alert is staying where it should be and ignoring scans while the alert window is up just how I want it.
So my question is, how do I capture the return key and prevent the Alert from calling the dismiss action? I'm a bit new with swift and I have a feeling the answer is simple, but I've tried half a dozen things that just isn't working.
If there is a setting to prevent all user input to the alert window or anything solution, I'm all for any method. I just rather not use the ActionSheet, and prefer to use the iOS alerts instead of creating my own screen. If this is not possible, I'm sure I can build my own 'alerts' window.
Code, that I'm calling from a simple Alerts class I made.
import UIKit
class Alerts {
var controller: UIViewController
var message: String
var title: String
init?(title: String, message: String, controller: UIViewController){
if title.isEmpty || message.isEmpty {
return nil
}
self.title = title
self.message = message
self.controller = controller
}
func save_alert(input_field:UITextField, callable: (Bool)->Void ){
let action = UIAlertAction(title: "Dismiss", style: UIAlertActionStyle.Default) {
UIAlertAction in
callable(false)
input_field.enabled = true
input_field.becomeFirstResponder()
print("DISMISS CALLED")
}
let alert = UIAlertController(title: self.title,message:self.message,preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(action)
self.controller.presentViewController(alert,animated:true, completion: nil)
}
}
Try something like this.
func save_alert(input_field:UITextField, callable: (Bool)->Void ){
let action = UIAlertAction(title: "Dismiss", style: UIAlertActionStyle.Default) {
UIAlertAction in
callable(false)
input_field.enabled = true
input_field.becomeFirstResponder()
print("DISMISS CALLED")
showAlert()
}
showAlert()
}
func showAlert() {
let alert = UIAlertController(title: self.title,message:self.message,preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(action)
self.controller.presentViewController(alert,animated:true, completion: nil)
}
So I have a button that opens the app store page for the app via "UIApllication.sharedApplication().openURL"
What is strange is that instead of popping up with a regular Web browser it opens to the app store page.
The problem is that there is a cancel button on the page it opens. And that button does not seem to work. Any ideas how I might implement that button?
Here is the code: (vc() is just a function that returns the view controller)
static func rate()
{
crashLog("Opening up itunes page")
//https://itunes.apple.com/us/app/toothache-be-the-monster/id1033405285?mt=8
if let v = vc()
{
let url = NSURL(string: "itms://itunes.apple.com/de/app/toothache-be-the-monster/id1033405285?mt=8&uo=4")
if UIApplication.sharedApplication().canOpenURL(url!) == true {
UIApplication.sharedApplication().openURL(url!)
}
}
neverRateAgain()
}
I testet your code.
The problem is that you are using itms which will open the iTunes Store.
Simpy use itms-apps instead of itms so it will be opened in the App Store rather then the iTunes Store. (Don't forget to change it in you .plist too)
let url = NSURL(string: "itms-apps://itunes.apple.com/de/app/toothache-be-the-monster/id1033405285?mt=8&uo=4")
EDIT: Now that you added more code, let me show you how I achieved what you wanted to achieve.
CODE:
func showRateMe() {
let alert = UIAlertController(title: "Would you like to rate the App?", message: "We hope you enjoy the App !", preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Rate App now !", style: UIAlertActionStyle.Default, handler: { alertAction in
UIApplication.sharedApplication().openURL(NSURL(string : "https://itunes.apple.com/us/app/name/id")!)
alert.dismissViewControllerAnimated(true, completion: nil)
}))
alert.addAction(UIAlertAction(title: "Later!", style: UIAlertActionStyle.Default, handler: { alertAction in
alert.dismissViewControllerAnimated(true, completion: nil)
}))
self.presentViewController(alert, animated: true, completion: nil)
alert.addAction(UIAlertAction(title: "NO !", style: UIAlertActionStyle.Default, handler: { alertAction in
NSUserDefaults.standardUserDefaults().setBool(true, forKey: "neverRate")
alert.dismissViewControllerAnimated(true, completion: nil)
}))
}
SOLUTION (I think):
I use in my code: "https://itunes.apple.com/us/app/name/id"
You use in yours: "itms://itunes.apple.com/de/app/toothache-be-the-monster/id1033405285?mt=8&uo=4"
Yours should be: "https://itunes.apple.com/de/app/toothache-be-the-monster/id1033405285?mt=8&uo=4"
OLD ANSWER THAT APPLIED TO A PREVIOUS VERSION OF THE QUESTION:
If I am not mistaken, this is what you need to do:
1) Create a cancel button with an IBAction.
2) IBAction does this:
self.dismissViewController()
3) Done.
This is how I would "implement the Cancel button" as you wrote in your question.
When using UIAlertController like this:
var alert = UIAlertController(title: "Core Location",
message: "Location Services Disabled!",
preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default,
handler: nil))
self.navigationController.presentViewController(alert, animated: true,
completion: nil)
I noticed that the dismissal of the alert view is seemingly done automatically.
Shouldn't the dismissal of a presented ViewController be done by the presenting ViewController via a delegate call?
The dismissal is "included" in the presentViewController call. You do not need a delegate because you have the completion block. In this block you put what you would normally put into the delegate callback, except the call to dismiss the alert.
As far as "best practice" is concerned, I noted that in many APIs, Apple replaced delegate callbacks with completion blocks. Apple typically recommends using the block syntax. I surmise this could be partly because it helps keeping the related code sections together.
Is some Cases you may like to use this:
class MyAlertController : UIAlertController {
var dismissHandler : (()->())?
override func viewDidDisappear(_ animated: Bool) {
dismissHandler?()
super.viewDidDisappear(animated)
}
}
Usage:
let alert = MyAlertController(title: ...
let cancelButton = UIAlertAction(titl
alert.dismissHandler = { /*...do something */ }
alert.addAction(cancelButton)
...
There is an elegant way! Just write the action or function inside the alert controller's cancel action. (here the action style should be .cancel)
Code for Swift 3:
let Alert: UIAlertController = UIAlertController(title: nil, message: nil, preferredStyle: UIAlertControllerStyle.actionSheet)
let OkAction: UIAlertAction = UIAlertAction(title: “Ok”, style: .default) { ACTION in
//Will be called when tapping Ok
}
let CancelAction: UIAlertAction = UIAlertAction(title: "Cancel", style: .cancel) {ACTION in
// Will be called when cancel tapped or when alert dismissed.
// Write your action/function here if you want to do something after alert got dismissed”
}
Alert.addAction(OkAction)
Alert.addAction(CancelAction)
present(Alert, animated: true, completion: nil)