How To Move To Spesific Screen iOS When User Tap Notification FCM - swift

I made a notification, now my notification has appeared on iOS when I tap the notifications bagde the notification appears I want to switch to another View Controller page.
This my code in AppDelegate.swfit
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
// If you are receiving a notification message while your app is in the background,
// this callback will not be fired till the user taps on the notification launching the application.
// TODO: Handle data of notification
// With swizzling disabled you must let Messaging know about the message, for Analytics
// Messaging.messaging().appDidReceiveMessage(userInfo)
// Print message ID.
if let messageID = userInfo[gcmMessageIDKey] {
print("Message ID: \(messageID)")
}
// Print full message.
print("user data msg \(userInfo)")
guard let aps = userInfo["aps"] as? [String : AnyObject] else {
print("Error parsing aps")
return
}
print(aps)
if let alert = aps["alert"] as? String {
body = alert
} else if let alert = aps["alert"] as? [String : String] {
body = alert["body"]!
title = alert["title"]!
}
if let alert1 = aps["category"] as? String {
msgURL = alert1
}
print("Body\(body)")
print(title)
print(msgURL)
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyBoard.instantiateViewController(withIdentifier: "register")
vc.modalPresentationStyle = .overFullScreen
present(vc, animated: true)
}
}
But I got error: Cannot find 'present' in scope Where I should put my code for naviagtion when the user got a notification in Swift.

present() is a method on UIViewController, so you'll have to have a reference to a view controller that you can call that method on.
This can vary by the structure of your app -- especially if you're using a SceneDelegate, you have multiple windows, etc.
My suggestion is that you post a Notification that you can listen for in whatever has a reference to your top view controller. For example, instead of where you have present, you could do something like this:
let dataDict : [String : UIViewController] = ["vc": vc]
NotificationCenter.default.post(name: Notification.Name("ShowVC"), object: nil, userInfo: dataDict)
Then, assuming you're using a SceneDelegate, you could do this in your scene(_ scene: UIScene, willConnectTo) function:
NotificationCenter.default.publisher(for: Notification.Name("ShowVC"))
.compactMap {
$0.userInfo as? [String: UIViewController]
}
.sink {
guard let vc = $0["vc"] else {
return
}
window?.rootViewController?.present(vc, animated: true)
}.store(in: &cancelSet)
At the top of your file you'll need to import Combine and then your SceneDelegate will need a new property in order for the above code to work:
var cancelSet: Set<AnyCancellable> = []
Even if you aren't using a SceneDelegate, the basic concepts here should apply to other scenarios.
Note, however, that this is not a foolproof plan -- the root view controller has to be the visible view in order for this to work. This all may depend on your architecture. Search SO for "topmost view controller" to see plenty of discussion on this topic.

Related

How to show an Alert dialog for push notifications when not using a ViewController

I am building a SwiftUI app using Azure NotificationHubs and struggling with how to show an alert when a notification is received if I am not using a ViewController.
I am getting the notification, but don't know how to show a dialog from an event in AppDelegate since all the examples I have found are for ViewControllers.
func notificationHub(_ notificationHub: MSNotificationHub, didReceivePushNotification message: MSNotificationHubMessage) {
print("notificationHub...")
let title = message.title ?? ""
let body = message.body ?? ""
print("title: \(title)")
print("body: \(body)")
let userInfo = ["message": message]
NotificationCenter.default.post(name: NSNotification.Name("MessageReceived"), object: nil, userInfo: userInfo)
guard let aps = message.userInfo["aps"] as? [String: AnyObject] else {
return
}
print("aps: \(aps)")
if (UIApplication.shared.applicationState == .background) {
print("Notification received in background")
} else {
print("Notification received in foreground")
}
}
I don't get the message when in the background but when I click on the notification when the app is in the background I get the Notification received in foreground message.
I suppose the desired behavior is that if they click on message while the app is in the background it will go to the appropriate view. When it's in the foreground it will ask the user if they want to go to the view.
I have the navigation piece working, just need to know how to alert the user and respond to their choice.
In the Azure examples on GitHub they show:
class SetupViewController: MSNotificationHubDelegate // And other imports
// Set up the delegate
MSNotificationHub.setDelegate(self)
// Implement the method
func notificationHub(_ notificationHub: MSNotificationHub!, didReceivePushNotification notification: MSNotificationHubMessage!) {
let title = notification.title ?? ""
let body = notification.body ?? ""
if (UIApplication.shared.applicationState == .background) {
NSLog("Notification received in background: title:\"\(title)\" body:\"\(body)\"")
} else {
let alertController = UIAlertController(title: title, message: body, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "OK", style: .cancel))
self.present(alertController, animated: true)
}
}
but I am not using a ViewController. This was a great resource as well but again they show a view controller (class ViewController: UIViewController).
I am new to iOS and Swift so I might be looking in the wrong area but I am essentially trying to show an alert dialog from an event triggered in the AppDelegate.
UPDATE
While working through the response below, I did come across this post recommending that I create a view with a #State private var showAlert = false; that could be bound to something I guess I change in the AppDelegate notificationHub method.
Is this the proper approach in SwiftUI vs UIWindowScene or UIApplication?
I did get this to work:
if UIApplication.shared.windows.first != nil {
UIApplication.shared.windows.first?.rootViewController!.present(alertController, animated: true)
}
You can get the rootViewController from UIApplication.shared.windows.first, then present an UIAlertController.
Usage:
//Force
UIApplication.shared.windows.first?.rootViewController!.present(..)
//Safe
guard let viewController = UIApplication.shared.windows.first?.rootViewController else {return}
viewController.present(...)
Note:
UIApplication.shared.windows is deprecated in iOS 15, you can use UIWindowScene.windows.first in iOS 16.

Using openTok after accept a Call (CallKit)

I am working on a project which requires opentok and callkit for notifying users. However, the application keeps crashing when openTok tries to connect to a session. IDK what is going on right now. Here is my work flow and codes:
Push notification about available opentok session -> start incoming call -> user accept the call -> start the opentok storyboard -> do some backend stuff -> connect to a session !!!! THIS IS WHEN IT CRASHES.
ERROR:
Thread 1: EXC_BAD_ACCESS (code=2, address=0x1968a0ad8)
Besides, I would like to ask for advice about receive notification. Instead of showing the notification on the screen. I would like to start the call, using callkit, like whatsapp or fb messenger. Please give me a hint about this also. Right now, I can only start the call when the app is in foreground when push notification is sent.
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
// If you are receiving a notification message while your app is in the background,
// this callback will not be fired till the user taps on the notification launching the application.
// TODO: Handle data of notification
// With swizzling disabled you must let Messaging know about the message, for Analytics
// Messaging.messaging().appDidReceiveMessage(userInfo)
// Print message ID.
if let messageID = userInfo[gcmMessageIDKey] {
print("Message ID: \(messageID)")
}
// Print full message.
print(userInfo)
let aps = userInfo["aps"] as! [String: AnyObject]
// 1
if aps["content-available"] as? Int == 1 {
let uuid = UUID(uuidString: MyVariables.uuid)
AppDelegate.shared.displayIncomingCall(uuid: uuid!, handle: "Sanoste", hasVideo: false) { _ in
}
}else {
return
}
completionHandler(UIBackgroundFetchResult.newData)
}
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
action.fulfill()
AppDelegate.shared.openTok()
}
func openTok() {
let mainStoryboard = UIStoryboard(name: "callView", bundle: nil)
let vc = mainStoryboard.instantiateViewController(withIdentifier: "callViewController") as! callViewController
vc.view.frame = UIScreen.main.bounds
UIView.transition(with: self.window!, duration: 0.5, options: .transitionCrossDissolve, animations: {
self.window!.rootViewController = vc
}, completion: nil)
}
// Join a session from MBE
func joinSession() {
var error: OTError?
pSession = OTSession(apiKey: openTokSessionKey.pApiKey, sessionId: openTokSessionKey.pSessionId, delegate: self as OTSessionDelegate)
pSession?.connect(withToken: openTokSessionKey.pToken, error: &error)
if error != nil {
print(error!)
}
sSession = OTSession(apiKey: openTokSessionKey.sApiKey, sessionId: openTokSessionKey.sSessionId, delegate: self as OTSessionDelegate)
sSession?.connect(withToken: openTokSessionKey.sToken, error: &error)
if error != nil {
print(error!)
}
}
Anyone helps please ?
This crash is a kind of warning. If you disable "Main Thread Sanitizer" in your project settings in Xcode 9 and rebuild your project, can fix the issue.

Open a ViewController from remote notification

I try to open a particular ViewController when my app catch a remote notification.
Let me show my project's architecture.
Here my storyboard :
When I receive a notification I want open a "SimplePostViewController", so this is my appDelegate :
var window: UIWindow?
var navigationVC: UINavigationController?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let notificationTypes: UIUserNotificationType = [UIUserNotificationType.Alert, UIUserNotificationType.Badge, UIUserNotificationType.Sound]
let pushNotificationSettings = UIUserNotificationSettings(forTypes: notificationTypes, categories: nil)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
self.navigationVC = storyboard.instantiateViewControllerWithIdentifier("LastestPostsNavigationController") as? UINavigationController
application.registerUserNotificationSettings(pushNotificationSettings)
application.registerForRemoteNotifications()
return true
}
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
if let postId = userInfo["postId"] as? String {
print(postId)
let api = EVWordPressAPI(wordpressOauth2Settings: Wordpress.wordpressOauth2Settings, site: Wordpress.siteName)
api.postById(postId) { post in
if (post != nil) {
self.navigationVC!.pushViewController(SimplePostViewController(), animated: true)
} else {
print("An error occurred")
}
}
}
}
I save my UINavigationViewController when the app is launch and simply try to push a new SimplePostViewController when I receive a notification. But nothing happen.
I placed breakpoints and seen that my pushViewController method was reached, but not the ViewWillAppear of my SimplePostViewController.
I also used the "whats new" view add perform my segue but nothing happen too.
Solution :
for child in (self.rootViewController?.childViewControllers)! {
if child.restorationIdentifier == "LastestPostsNavigationController" {
let lastestPostsTableViewController = (child.childViewControllers[0]) as! LastestPostsTableViewController
let simplePostVC = (self.storyboard?.instantiateViewControllerWithIdentifier("PostViewController"))! as! PostViewController
simplePostVC.post = post
lastestPostsTableViewController.navigationController?.pushViewController(simplePostVC, animated: true)
}
}
I use :
child.childViewControllers[0]
because I've only one child in my example.
I created a sample project with a local notification instead of a remote notification for ease of showing the functionality but it should be as simple as setting the root view controller of the window in the app delegate didreceiveremote notification.
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Subscribe for notifications - assume the user chose yes for now
application.registerUserNotificationSettings(UIUserNotificationSettings(forTypes: [.Alert, .Badge, .Sound], categories: nil))
return true
}
func applicationDidEnterBackground(application: UIApplication) {
//Crete a local notification
let notification = UILocalNotification()
notification.alertBody = "This is a fake notification"
notification.fireDate = NSDate(timeIntervalSinceNow: 2)
UIApplication.sharedApplication().scheduleLocalNotification(notification)
}
func application(application: UIApplication, didReceiveLocalNotification notification: UILocalNotification) {
let sb = UIStoryboard(name: "Main", bundle: nil)
let otherVC = sb.instantiateViewControllerWithIdentifier("otherVC") as! OtherViewController
window?.rootViewController = otherVC;
}
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
//Your code here
}
`
You need to worry about managing your view hierarchy and sending anything to it that you need to send from the notification user data.
In my example, I create a local notification when you close the app that fires after a view seconds. If you then launch the app from the notification, it will open the "other view controller" which would be the "SimplePostViewController" in your case.
Also, be sure that you are registering for remote notifications in the didFinishLaunchWithOptions.
Github very simple sample : https://github.com/spt131/exampleNotificationResponse

CloudKit Subscription Woes

I am struggling with the following subscription:
let predicate = NSPredicate(format: "gc_alias != %# AND distanceToLocation:fromLocation:(%K,%#) < %f",
self.localPlayer!.alias!,
"location",
self.currentLocation!,
10)
let subscription = CKSubscription(recordType: "Player", predicate: predicate, options: .FiresOnRecordCreation)
subscription.zoneID = nil
let notification = CKNotificationInfo()
notification.alertBody = "Nearby Player within Range!"
notification.soundName = UILocalNotificationDefaultSoundName
subscription.notificationInfo = notification
let container = CKContainer.defaultContainer()
let publicDb = container.publicCloudDatabase
publicDb.saveSubscription(subscription) { (result, error) -> Void in
if error != nil {
print(error!.localizedDescription)
} else {
print("SUBSCRIBED SUCCESS")
print(result)
NSUserDefaults.standardUserDefaults().setBool(true, forKey: "subscribed")
}
}
Basically, when a new Player record is created or updated I log the user's location.
I want user A to be notified via Push when user B creates or updates their Player record and is within 10KM.
I believe I have the push permissions setup correctly in my app (user is prompted to confirm this before their sub is created, for example).
No pushes arriveth. Any ideas? Am I suffering from some fundamental CK misconception?
You don't seem to be registering for push notifications:
iOS Developer Library: Subscribing to Record Changes
Saving subscriptions to the database doesn’t automatically configure your app to receive notifications when a subscription fires. CloudKit uses the Apple Push Notification service (APNs) to send subscription notifications to your app, so your app needs to register for push notifications to receive them.
According to Hacking with Swift: Delivering notifications with CloudKit push messages: CKSubscription and saveSubscription you should:
Go to AppDelegate.swift and put this code into the didFinishLaunchingWithOptions method:
let notificationSettings = UIUserNotificationSettings(forTypes: [.Alert, .Sound], categories: nil)
UIApplication.sharedApplication().registerUserNotificationSettings(notificationSettings)
UIApplication.sharedApplication().registerForRemoteNotifications()
And
For the sake of completion, you could optionally also catch the didReceiveRemoteNotification message sent to your app delegate, which is called if a push message arrives while the app is running. Something like this ought to do the trick:
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
if let pushInfo = userInfo as? [String: NSObject] {
let notification = CKNotification(fromRemoteNotificationDictionary: pushInfo)
let ac = UIAlertController(title: "What's that Whistle?", message: notification.alertBody, preferredStyle: .Alert)
ac.addAction(UIAlertAction(title: "OK", style: .Default, handler: nil))
if let nc = window?.rootViewController as? UINavigationController {
if let vc = nc.visibleViewController {
vc.presentViewController(ac, animated: true, completion: nil)
}
}
}
}

How to call viewcontroller method from AppDelegate if I use TabBar Navigation

I really need a help with calling view controller method as soon as my app open when the remote notification comes in especially when using TabBar navigation.When I open my app when the notification arrive,it will call didReceiveRemoteNotification because I enabled "content-available = 1".But,the problem is I don't know how to access my HomeViewController method refresh() because I use TabBar Navigation.I need to call HomeViewController refresh() method inside didReceiveRemoteNotification because it really need to refresh the data from core and the notification that arrived are saved at coredata.
Everytime the notification comes in,I save them at realm database and show user at HomeViewController
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler handler: (UIBackgroundFetchResult) -> Void) {
if let aps = userInfo["aps"] as? NSDictionary{
if let alert = aps["alert"] as? NSDictionary{
if let mbody = alert["body"] as? String{
print("Message Body : \(body)")
body = mbody
}
if let mtitle = alert["title"] as? String{
print("Message Title : \(title)")
title = mtitle
}
}
}
let newNotification = NotificationList()
newNotification.title = title
newNotification.body = body
//Realm insert query
oneSignalHelper.insertOneSignalNotification(newNotification)
print("Remote Receive")
//This condition isn't working
if let viewController = self.window?.rootViewController as? HomeViewController {
print("Remote Receive Read")
//This method is HomeViewController method which I gonna use for refresh when I tapped notification.It read that are save in realm database and show results.
viewController.readNotificationsAndUpdateUI()
}
handler(UIBackgroundFetchResult.NewData)
}
This is readNotificationsAndUpdateUI() from HomeViewController
func readNotificationsAndUpdateUI(){
notifications = realm.objects(NotificationList)
self.notificationTableView.setEditing(false, animated: true)
self.notificationTableView.reloadData()
self.refreshControl?.endRefreshing()
}
I suggest you using NSNotificationCenter, just post notification inside didReceiveRemoteNotification like this:
NSNotificationCenter.defaultCenter().postNotificationName("refreshNotification", object: nil)
In your HomeViewController in viewDidLoad() you should subscribe to the notification center like this:
NSNotificationCenter.defaultCenter().addObserver( self, selector: "refresh", name: "refreshNotification", object: nil)
Not sure if it helps but I solved it this way, based on previous answer
Load Storyboard
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
Load ViewController (check that you have an identifier in your storyboard for this ViewController!)
let bestellvorschlag = storyBoard.instantiateViewController(identifier: "Bestellvorschlag") as! Bestellvorschlag
Load Function on ViewController
bestellvorschlag.loaddata() //Loads CoreData into Array of artikels
Maybe Modify the Tabbar Badge with the number of Results
let tabbarcontroller = self.window?.rootViewController as! UITabBarController
if bestellvorschlag.artikels.count > 0
{
if let tabItems = tabbarcontroller.tabBar.items {
// Third Tab!
let tabItem = tabItems[2]
tabItem.badgeValue = bestellvorschlag.artikels.count.description
}
}
Complete Function in appDelegate
func applicationDidBecomeActive(_ application: UIApplication) {
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
let bestellvorschlag = storyBoard.instantiateViewController(identifier: "Bestellvorschlag") as! Bestellvorschlag
bestellvorschlag.loaddata()
let tabbarcontroller = self.window?.rootViewController as! UITabBarController
if bestellvorschlag.artikels.count > 0
{
if let tabItems = tabbarcontroller.tabBar.items {
// Third Tab!
let tabItem = tabItems[2]
tabItem.badgeValue = bestellvorschlag.artikels.count.description
}
}
}