call a specific View Controller when remotepush notification is clicked in swift - swift

I want to show push notification in a specific view controller when tap on notification and also want to send data from notification to view controller. I am using swift for development

As #luzo pointed out, Notifications are the way to send to communicate to view controller(s) that an event has happened. The notification also has a userinfo parameter that accepts a dictionary of data you would like to send together with the notification to the view controller.
In Swift 3, add this to the tap button:
let center = NotificationCenter.default
center.post(name: Notification.Name(rawValue: "nameOfNotification"),
object: nil,
userInfo:["id":"data"])
And in the viewcontroller, register for the id of the notification and add a function reference:
let center = NotificationCenter.default
center.addObserver(forName:NSNotification.Name(rawValue: "nameOfNotification"), object:nil, queue:nil, using:notifDidConnect)
and add the function implementation:
func notifDidConnect(notification:Notification) -> Void {
guard let userInfo = notification.userInfo,
let id = userInfo["id"] as? String else {
print("error occured")
return
}
print("notification received")
}

You should implement router for your view controllers which will be listening to notification send from app delegate and he will decide what to do. This is how I would do it, might be a better solution.

Related

Responding to EKEventStore Change in SwiftUI

According to the Apple Developer Documentation:
An EKEventStore object posts an EKEventStoreChanged notification whenever it detects changes to the Calendar database
The article then goes on to provide examples of how to do this in UIKit:
NotificationCenter.default.addObserver(self, selector: Selector("storeChanged:"), name: .EKEventStoreChanged, object: eventStore)
In SwiftUI, I tried to do it this way:
let publisher = NotificationCenter.default.publisher(for: NSNotification.Name("EKEventStoreChanged"))
and
.onReceive(publisher) { value in
print("Store Changed")
}
But I never received any notification, and therefore the print statement was never called.
I also tried this:
let publisher = NotificationCenter.default.publisher(for: NSNotification.Name.EKEventStoreChanged)
but nothing changed.
My Question: How do I receive the EKEventStoreChanged notification in SwiftUI?
Any help would be appreciated.

How to process deep link from an aps notification using the same #EnvironmentObject used in onOpenUrl without using a singleton in AppDelegate

I am trying to coordinate my deep link with push notifications so they both process my custom url scheme in the same manner and navigate to the appropriate view. The challenge seems to be with push notifications and how to process the link passed, through an apn from Azure Notification Hubs, using the same #EnvironmentObject that the onOpenUrl uses without breaking the SwiftUI paradigm and using a singleton.
Here is how I trigger the notification on my simulator, which works fine and navigates me to the appropriate view:
xcrun simctl openurl booted "myapp://user/details/123456"
Which triggers this the onOpenUrl in this code:
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(sessionInfo)
.onOpenURL { url in
print("onOpenURL: \(url)")
if sessionInfo.processDeepLink(url: url) {
print("deep link TRUE")
} else {
print("deep link FALSE")
}
}
}
}
And all my DeepLinks work just as desired. I wanted to trigger them from a notification so I created an apns file with the same link that worked using xcrun:
{
"aps": {
"alert": { // alert data },
"badge": 1,
"link_url":"myapp://user/details/123456"
}
}
and pushed it to the simulator like this:
xcrun simctl push xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx com.myco.myapp test.apn
How do I reference my object from the AppDelegate which gets the message:
func notificationHub(_ notificationHub: MSNotificationHub!, didReceivePushNotification message: MSNotificationHubMessage!) {
print("notificationHub...")
let userInfo = ["message": message!]
print("user: ")
NotificationCenter.default.post(name: NSNotification.Name("MessageReceived"), object: nil, userInfo: userInfo)
if (UIApplication.shared.applicationState == .background) {
print("Notification received in the background")
} else {
print("Notification received in the foreground")
}
UIApplication.shared.applicationIconBadgeNumber = 4
}
I looked at this post, but couldn't relate the components to my app, possibly due to the NotificationHub part of it. I also saw this post, but again didn't know how to connect it.
I saw this post and it looks like push notification and deep linking are two different things. I could use the same logic if I could access the SessionInfo object from the AppDelegate. I'm concerned about messing around in there given I'm new to iOS development. Here is what I'm using in the App:
#StateObject var sessionInfo: SessionInfo = SessionInfo()
This post seems to cover it but I'm still having trouble. I don't know what this means:
static let shared = AppState()
And my SessionInfo is decorated with #MainActor. When I access it from other places I use:
#EnvironmentObject var sessionInfo: SessionInfo
I also saw this post, which there was no selected answer, but the one which did exist seemed to recommend making the AppDelegate and EnvrionmentObject and push it into the ContentView. I think what I really need is the AppDelegate when the notification arrives to update something shared/published to the SessionInfo object so the url is parsed and the navigation kicked off. This seems backwards to me.
This post makes the AppDelegate an ObservableObject with a property which is published and makes the AppDelegate an EnvrionmentObject, so when the value is updated, it's published. If it were the navigation link/object that would work but something would still need to process it and it would not make sense for the onOpenUrl to use the AppDelegate, so again I think this is backwards.
If I did follow the post where there is a static SessionInfo object in the SessionInfo class, singleton, that means I would need to remove the #EnvironmentObject var sessionInfo: SessionInfo from the ContentView and the .environmentObject(sessionInfo) on the main View I am using I think and instead instantiate the shared object in each view where it is used. Right? It seems like I followed this whole #EnvrionmentObject, #StateObject, #MainActor paradigm and would have to abandon it. I'm not sure if that is right or what the tradeoffs are.
Most recently this post seems to be pretty in-depth, but introduces a new element, UNUserNotificationCenter, which I heard referenced in this youtube video.
This article was very helpful for the notification part.
Azure NotificationHubs the message info is in message.userInfo["aps"] vs userInfo["aps"] in the example or most places I have seen it. Not much documentation on MSNotificationHubMessage:
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
}
...
}
Second, this post provided the answer which I adapted for my project:
final class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate, MSNotificationHubDelegate {
var navMgr: NavigationManager = NavigationManager()
...
}
and
#UIApplicationDelegateAdaptor private var appDelegate: AppDelegate
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(sessionInfo)
.environmentObject(appDelegate.navMgr)
.onOpenURL { url in
print("onOpenURL: \(url)")
if sessionInfo.processDeepLink(url: url) {
print("deep link TRUE")
} else {
print("deep link FALSE")
}
}
}
}

WatchOS: Pass data from one controller to another through "Next Page" segue

I am loading 3 interfacecontrollers with WKInterfaceController.reloadRootControllers with contexts in my app.
Everything works just fine. However, I need to pass data from Page 1 to Page 2 on swipe. It seems there is not segue that I can use programmatically to create a new context or pass data another way.
How could I solve this?
I don't want to create a singleton just for this. I know how to pass data in contexts with other segues, this question specifically relate to "Next Page" navigation on Watchkit. I couldn't find an answer so far.
Thanks!
Markus
Have you tried using contextForSegue in your first page?
override func contextForSegue(withIdentifier segueIdentifier: String) -> Any? {
let myDate = "today"
return myDate
}
I'm been looking around for an answer for this question also..
NextPage Segue is similar to UIPageViewController in iOS. I have tried contextForSegue(withIdentifier segueIdentifier: String) but it is not getting called on swipe, and you can't give this segue an identifier in the storyboard.
The only way around it for me was to use NotificationCenter
In the first InterfaceController I post a notification with the object on willDisappear/didDeactivate doesn't really make a difference with watchOS and on the second InterfaceController I subscribed to it.
NotificationCenter.default.post(name: Notification.Name("interface1WillDisappear"), object: context)
NotificationCenter.default.addObserver(self, selector: #selector(awake(withNotification:)), name: Notification.Name("interface1WillDisappear"), object: nil)
Add I added this func
func awake(withNotification notification: Notification) {
guard let context = context as? MyContextObject else { return }
print(context)
}
Finally don't forget to remove the observer
deinit {
NotificationCenter.default.removeObserver(self)
}
Hope this helps!

Handle deep link notification

I'm adding deep linking to my app and getting stuck at handing off the URL to the view controller. I try to use notification but it doesn't work all the times.
My App Delegate:
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
let notification = Notification(name: "DeepLink", object: url)
NotificationCenter.default.post(notification)
return true
}
}
And View Controller:
class ViewController: UIViewController {
func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.handleDeepLink), name: "DeepLink", object: nil)
}
#objc private func handleDeepLink(notification: Notification) {
let url = notification.object as! URL
print(url)
}
}
Problem: when my app is cold launched the notification is not handled, which I guess is due to the View Controller not having time to register itself as the observer yet. If I press the Home button, go to Notes and click on the deep link a second time, everything works as it should.
Question: how can I register the View Controller to observe the notification when the app is cold launched? Or is there a better way to send the URL from the App Delegate to the View Controller?
I think an initial assumption is to assume your view controller does not have time to register itself which means that the connection between URL and View controller must be decoupled and reside outside of the view controller.
I would then use some kind of lookup to instantiate the view controller when a URL is received.
For example,
if url.contains(“x”) {
let vc = ViewController(...)
cv.url = URL // Pass contextual data to the view controller.
// Present or push cv
}
As your app gets more complex you have to manage more challenging scenarios, like standing-up entire controller stacks, or removing presented controllers.
I followed the example in the LaunchMe app. Here's what solved my problem:
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
// My root view controller is a UINavigationController
// Cast this to whatever class your root view controller is
guard let navigationController = self.window?.rootViewController as? UINavigationController else {
return false
}
// Navigate to the view controller that handles the URL within the navigation stack
navigationController.popToRootViewController(animated: false)
// Handle the URL
let vc = navigationController.topViewController as! ViewController
vc.handleDeepLink(url: url)
return true
}
Store if notification is not handled and load it when viewDidLoad.
var pendingNotificationInfo: PushNotificationInfo?

macOS Swift 3 - Handling notification alert action button

I'm new to Swift (but not to programming). I have simple app that provides an alert based on specific conditionals. I would like to execute a function (or even just set a variable) when one of the buttons is pressed. Ideally, I just need one button, but if for whatever reason, only the notification.actionButtonTitle can have a handler, that's fine with me.
My notification code is currently in a Swift file as a helper.
import Foundation
class NotificationHelper {
static func sampleNotification(notification: NSUserNotification) {
let notificationCenter = NSUserNotificationCenter.default
notification.identifier = "unique-id-123"
notification.hasActionButton = true
notification.otherButtonTitle = "Close"
notification.actionButtonTitle = "Show"
notification.title = "Hello"
notification.subtitle = "How are you?"
notification.informativeText = "This is a test"
notificationCenter.deliver(notification)
}
}
Currently in AppDelegate, this is defined:
let notification = NSUserNotification()
…and I call the notification like this:
NotificationHelper.sampleNotification(notification: notification)
The resulting notification works, as you can see in the screenshot below. However, I cannot seem to listen to the button action. I have tried adding this to the AppDelegate as well as the NotificationHelper file, but I did not have any success with it:
func userNotificationCenter(center: NSUserNotificationCenter, didActivateNotification notification: NSUserNotification) {
print("checking notification response")
}
Any idea of what I'm missing?
Thanks!
You'll need to assign something as the delegate of the NSUserNotificationCenter:
NSUserNotificationCenter.default.delegate = self
If you add this to your AppDelegate and make your AppDelegate conform to NSUserNotificationCenterDelegate:
class AppDelegate: NSObject, NSUserNotificationCenterDelegate {
}