I display a UIAlertController from the AppDelegate when a push notification is received and the app is open. The problem is, the alert does not show the first time the app is run. If you stop the app in Xcode and then re-start it, everything works as expected.
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
var alert: UIAlertController?
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (_ options: UNNotificationPresentationOptions) -> Void) {
self.showAlert(title: title,message:body,buttonTitle:"View",window:self.window!)
}
func showAlert(title: String,message : String,buttonTitle: String,window: UIWindow){
self.alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.alert)
alert?.addAction(UIAlertAction(title: buttonTitle, style: UIAlertActionStyle.default, handler: { (action) in
//self.messageDelegate?.goToMessages()
}))
alert?.addAction(UIAlertAction(title: "Cancel", style: UIAlertActionStyle.default, handler: nil))
window.rootViewController?.present(alert!, animated: false, completion: nil)
}
}
Related
So what i am trying to achieve is when a notification launch and the user tap on it, an Alert controller should appear with 2 options. However, when the app launch from the notification tap, nothing appears.
These codes are inside the AppDelegate.swift file
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
showAlert()
}
func showAlert() {
let alert = UIAlertController(title: "Confirm", message: "Confirm?", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "No", style: .destructive, handler: nil))
alert.addAction(UIAlertAction(title: "Yes", style: .default, handler: nil))
window?.rootViewController?.present(alert, animated: true, completion: nil)
}
var topVC: UIWindow? = UIWindow(frame: UIScreen.main.bounds)
topVC?.rootViewController = UIViewController()
let alert = UIAlertController(title: "Alert", message: "Notification Received", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .cancel) { _ in
// action OK
})
topVC?.makeKeyAndVisible()
topVC?.rootViewController?.present(alert, animated: true, completion: nil)
Try this:
func showAlert() {
var alertController = UIAlertController(title: "Confirm", message: "Confirm?", preferredStyle: UIAlertControllerStyle.alert)
var okAction = UIAlertAction(title: "Yes", style: UIAlertActionStyle.Default) {
UIAlertAction in
// action
}
var cancelAction = UIAlertAction(title: "No", style: UIAlertActionStyle.Cancel) {
UIAlertAction in
// action
}
alertController.addAction(okAction)
alertController.addAction(cancelAction)
self.window?.rootViewController?.presentViewController(alertController, animated: true, completion: nil)
}
I think you are calling the func showAlert() from the wrong place.
When the app launch from the notification tap, the app gets an event in
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
So you should try this,
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
//Handle remote notification event on app launch
if let remoteNotification = launchOptions?[UIApplication.LaunchOptionsKey.remoteNotification] {
showAlert()
}
}
I am not too good with debugging and figuring out errors. So my app basically has a notification which when the action on the notification that says "Call" is pressed, a alert pops up and it calls the number that you had originally put in the UITextField when scheduling the notification. When the action brings me into the app for some reason I don't even get an alert and a Thread 1: EXC_BREAKPOINT error pops up. Any help would be awesome:) Thanks. Here is my code where the problem is likely coming from:
In my ViewController subclass:
func showAlert(title: String, message : String, buttonTitle1: String, buttonTitle2: String,window: UIWindow){
// create the alert
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.alert)
// add the actions (buttons)
alert.addAction(UIAlertAction(title: buttonTitle1, style: UIAlertActionStyle.default, handler: { action in
if let url = URL(string: "tel://\(self.phoneNumber.text)") {
UIApplication.shared.open(url, options: [:])
}
}))
alert.addAction(UIAlertAction(title: buttonTitle2, style: UIAlertActionStyle.cancel, handler: nil))
// show the alert
self.present(alert, animated: true, completion: nil)
}
//Main Stuff
var window: UIWindow?
And a ViewController extension:
extension ViewController: UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
if response.actionIdentifier == "call" {
self.showAlert(title: "Enter Call", message: "Are you sure?", buttonTitle1: "Go", buttonTitle2: "Cancel", window: self.window!)
}
}
}
To turn off unexpected breakpoints, you can go to the "Breakpoint Navigator".
Along the left side of your Xcode window, you'll see the icon that looks like a trapezoid (it's the red circle in the top left of this screenshot):
And while I simply have one breakpoint currently set in my screenshot, you might have more breakpoints set in your project (by accident). If you toggle the blue flag, the breakpoint will be disabled. And you can drag and pull that flag away from the column and the breakpoint will be deleted.
You can also hold down the control key while clicking on the flag and you'll see a pop-up menu that allows you to do the same thing.
Lastly, there's a "Deactivate Breakpoints" menu option in Xcode's "Debug" menu, where you can toggle the menu between "Deactivate" and "Activate".
You are likely getting a crash because you are initiating UI activities outside of the main thread. Try wrapping your UIAlert call in a DispatchQueue like this:
func showAlert(title: String, message : String, buttonTitle1: String, buttonTitle2: String,window: UIWindow){
DispatchQueue.main.async {
// create the alert
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.alert)
// add the actions (buttons)
alert.addAction(UIAlertAction(title: buttonTitle1, style: UIAlertActionStyle.default, handler: { action in
if let url = URL(string: "tel://\(self.phoneNumber.text)") {
UIApplication.shared.open(url, options: [:])
}
}))
alert.addAction(UIAlertAction(title: buttonTitle2, style: UIAlertActionStyle.cancel, handler: nil))
// show the alert
self.present(alert, animated: true, completion: nil)
}
}
This question already has answers here:
Attempt to present UIViewController on UIViewController whose view is not in the window hierarchy
(38 answers)
Closed 6 years ago.
I am trying to present AlertView once the user clicks on the local notification. The AlertView has options of cancel or ok.
extension ViewController:UNUserNotificationCenterDelegate{
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
print("Tapped in notification")
if(defaults.object(forKey: "alertOn") != nil){
// Create the alert controller
let alertController = UIAlertController(title: "Some text", message: "Some text again", preferredStyle: .alert)
// Create the actions
let okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.default) {
UIAlertAction in
}
let cancelAction = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.cancel) {
UIAlertAction in
}
// Add the actions
alertController.addAction(okAction)
alertController.addAction(cancelAction)
// Present the controller
self.present(alertController, animated: true, completion: nil)
}
}
func triggerNotification(){
print("notification will be triggered in five seconds..Hold on tight")
let content = UNMutableNotificationContent()
content.title = "SomeText"
content.body = "Some more text"
content.sound = UNNotificationSound.default()
let trigger = UNTimeIntervalNotificationTrigger.init(timeInterval: 5.0, repeats: false)
let request = UNNotificationRequest(identifier:requestIdentifier, content: content, trigger: trigger)
UNUserNotificationCenter.current().delegate = self as! UNUserNotificationCenterDelegate
UNUserNotificationCenter.current().add(request){(error) in
if (error != nil){
print(error?.localizedDescription)
}
}
}
It should show me the alertview with option to ok or cancel a request. Instead its showing me message UIAlertController whose view is not in the window hierarchy
When I put the alertview in viewdidapear it works fine but when I put it in userNotificationCenter my AlertView not get attached to the main view.
Brief View of Code
extension ViewController:UNUserNotificationCenterDelegate{
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
Present UIAlertView
}
}
ViewController{
Call to notification when app is in background
triggerNotification()
triggerNotification(){
Definition
}
}
try this
func currentTopViewController() -> UIViewController {
var topVC: UIViewController? = UIApplication.shared.delegate?.window?.rootViewController
while topVC?.presentedViewController {
topVC = topVC?.presentedViewController
}
return topVC!
}
and present the VC as
let currentTopVC: UIViewController? = self.currentTopViewController()
currentTopVC.present(alertController, animated: true, completion: nil)
I am developing in Swift 2.3
I have an Utils class enabling me to create UIAlertController easily.
public class Utils {
class func buildAlertInfo(withTitle title: String?, andMessage message: String?, withHandler handler: (UIAlertAction -> Void)?) -> UIAlertController {
let alertController = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.Alert)
alertController.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: handler))
return alertController
}
}
It enables me to build AlertController with ease :
let alert = Utils.buildAlertInfo(withTitle: "Information", andMessage: "John Snow is dying", withHandler: nil)
self.presentViewController(alert, animated: false, completion: nil)
My issue now is that I want to create an other type of custom Alert in my Utils Class.
For example an Alert with a Button that navigates the user to a specific ViewController.
I don't know how can I access a ViewController in a Custom Class. Maybe pass as a parameter the ViewController I want to present after the Button is tapped ?
Should I respect the MVC pattern and not interact with the View in my Utils Class ?
EDIT :
The Alert I want should looks like this :
class func buildAlertInfoWithFavButton(withTitle title: String?, andMessage message: String?, withHandler handler: (UIAlertAction -> Void)?) -> UIAlertController {
let alertController = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.Alert)
alertController.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: handler))
alertController.addAction(UIAlertAction(title: "Favorite", style: UIAlertActionStyle.Default, handler: handler))
return alertController
}
Where the OK action is the same, but the Favorite action should navigates you to the FavoriteViewController.
You can still use your buildAlertInfo function and can pass handler function like this way.
//Add function in your controller
func handler(action: UIAlertAction) {
//Add code of present
}
Now pass this function with your handler block
let alert = Utils.buildAlertInfo(withTitle: "Information",
andMessage: "John Snow is dying",
withHandler: self.handler)
**Edit:**For multiple action you can create array of handler with your method like this.
func buildAlertInfoWithFavButton(withTitle title: String?, andMessage message: String?, withHandler handler: [((UIAlertAction) -> Void)]?) -> UIAlertController {
alertController.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: handler.first))
alertController.addAction(UIAlertAction(title: "Favorite", style: UIAlertActionStyle.Default, handler: handler.last))
}
//Ok handler
func okHandler(action: UIAlertAction) {
//Add code of present
}
//Favorite handler
func favoriteHandler(action: UIAlertAction) {
//Add code of present
}
Now call the function like this.
let alert = Utils.buildAlertInfo(withTitle: "Information",
andMessage: "John Snow is dying",
withHandler: [okHandler, favoriteHandler])
What about
let alert = Utils.buildAlertInfo(
withTitle: "Information",
andMessage: "John Snow is dying",
withHandler: { action in self.go(to: specificViewController) }
)
self.presentViewController(alert, animated: false, completion: nil)
?
To be more Specific and use that method in any class of project. For this make a function in NSObject class. Like:
open class func showAlert(_ delegate: UIViewController, message: String ,strtitle: String, handler:((UIAlertAction) -> Void)! = nil)
{
let alert = UIAlertController(title: strtitle, message: message, preferredStyle: UIAlertControllerStyle.alert)
if handler == nil{
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil))
}
else
{
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: handler))
}
delegate.present(alert, animated: true, completion: nil)
}
In Controller I will call the method and do the required work like:
Alert.showAlert(self, message: "Message", strtitle: "Tittle!!", handler: {
(action : UIAlertAction) in
//Do your Work here
})
Note: Here Alert is name of NSObject class.
I would suggest using segue identifiers as the passed parameter (make sure you reference a segue that starts in the ViewController you call the "buildAlert" function from).
public class Utils {
class func buildAlertInfo(withTitle title: String?, andMessage message: String?, withSegue segueIdentifier: String?, sender: Any?) -> UIAlertController {
let alertController = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.Alert)
alertController.addAction(UIAlertAction(title: "OK", style: .Default, handler: handler: {
action in
self.performSegue(withIdentifier: segueIdentifier, sender: sender)
})
return alertController
}
This can also be achieved without creating a new function, just sending the handler part from above as a parameter to the function you already have, like this:
let alert = Utils.buildAlertInfo(withTitle: "Information", andMessage: "John Snow is dying", withHandler: {
action in
self.performSegue(withIdentifier: "mySegueIdentifier", sender: self)
})
EDIT: Note that the sender part can be any object that has an #IBOutlet reference in the ViewController the function call takes place
everyone!
When my app in foreground I want it to show alert when DidReceiveLocalNotification triggered.
I can add alert to mimic local notifications in AppDelegate.swift, but the problem is I don't know how to add method from my ViewController to UIAlertAction closure (see commented line), to finish animation when timer stopped.
My code below:
func application(application: UIApplication, didReceiveLocalNotification notification: UILocalNotification) {
let alertTimerEnds = UIAlertController(title: "Timer finished!", message: nil, preferredStyle: .Alert)
let okAction = UIAlertAction(title: "OK", style: .Default) { finished in
print("You've pressed OK button")
//self.ViewController().finishAnimation()
}
alertTimerEnds.addAction(okAction)
self.window?.rootViewController?.presentViewController(alertTimerEnds, animated: true, completion: nil)
}
Maybe I should do it in ViewController usind AppDelegate?
let someAppDelegate = UIApplication.sharedApplication().delegate as? AppDelegate
someAppDelegate?.application(UIApplication.sharedApplication(), didReceiveLocalNotification: UILocalNotification) { code for alert }
If you want to do that
func application(application: UIApplication, didReceiveLocalNotification notification: UILocalNotification)
{
var viewController : UIViewController = (application.keyWindow?.rootViewController)!
while ((viewController.presentedViewController) != nil) {
viewController = viewController.presentedViewController!
}
let alert = UIAlertController(title: "", message: notification.alertBody, preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.Default, handler: {(action: UIAlertAction!) in}))
viewController.presentViewController(alert, animated: true, completion: nil)
})
Show UIAlertController in didReceiveLocalNotification method
Try with below code, this will display alert on viewcontroller which is present at a moment.
func application(application: UIApplication, didReceiveLocalNotification notification: UILocalNotification) {
var latestViewController : UIViewController!
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
if let viewControllers = appDelegate.window?.rootViewController?.presentedViewController {
latestViewController = viewControllers as UIViewController
}
else if let viewControllers = appDelegate.window?.rootViewController?.childViewControllers {
latestViewController = viewControllers.last! as UIViewController
}
//var alert: UIAlertView!
//alert = UIAlertView(title: "Title", message:"Message , delegate: nil, cancelButtonTitle:"Ok" )
//alert.show()
let alert = UIAlertController(title: "Title", message:"Message", preferredStyle: .Alert)
let action = UIAlertAction(title: "OK", style: .Default) { _ in
// Put here any code that you would like to execute when
// the user taps that OK button (may be empty in your case if that's just
// an informative alert)
}
alert.addAction(action)
latestViewController.presentViewController(alert, animated: true){}
}
How do I migrate from UIAlertView (deprecated in iOS8)