watchOS: How to respond to app being opened via an intent? - swift

I've successfully added a StartWorkoutIntent to my watch app for when the action button is pressed, however I need to be notified when the app was opened via the intent. Apple's docs say to implement this handle function below which I have in my Extension Delegate, however when you press the action button it is never called?
func handle(startWorkout intent: INStartWorkoutIntent,
completion: #escaping (INStartWorkoutIntentResponse) -> Void) {
print("WE are called!")
// Let the app start the workout.
let response = INStartWorkoutIntentResponse(code: .continueInApp,
userActivity: nil)
completion(response)
}
This never gets called either:
func handle(_ userActivity: NSUserActivity) {
if userActivity.activityType == "Start Workout" {
print("Handle Start Workout called")
}
}

Related

Still receives INStartAudioCallIntent when drop support for iOS 12(INStartCallIntent)

I've meet a strange issue when dropping the support for iOS 12. When handle the user activity from AppDelegate continue userActivity, Although we drop INStartAudioCallIntent and INStartVideoCallIntent for it is deprecated in iOS 13, we still receive the above 2 intents from native contact card. But actually we want to handle INStartCallIntent instead.
Anyone knows why this happens for my debug version is 14.6, thanks.
Adding Intent as an app extension to handle INStartCallIntentHandling made my AppDelegate receive INStartCallIntent!
It can be implemented like this:
File > New -> Target -> Intent
Add INStartCallIntent to Supported Intents
Implement the IntentHandler (In the intent extension) like this:
class IntentHandler: INExtension, INStartCallIntentHandling {
override func handler(for intent: INIntent) -> Any {
return self
}
func handle(intent: INStartCallIntent, completion: #escaping (INStartCallIntentResponse) -> Void) {
let userActivity = NSUserActivity(activityType: NSStringFromClass(INStartCallIntent.self))
let response = INStartCallIntentResponse(code: .continueInApp, userActivity: userActivity)
completion(response)
}
}
The INStartCallIntent will now be called in the AppDelegate:
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
if let intent = userActivity.interaction?.intent {
if let intent = intent as? INStartCallIntent {
// The correct INStartCallIntent
}
}
}

open to deep link from local notification when app is inactive

I want to schedule local notification for X minutes and take user to a specified link when actioned.
currently when the app is in foreground or inactive the delegate method UNUserNotificationCenter(didReceive: withCompletionHandler:) is called and the app works as expected (the deep link opens)
the issue I'm running into is when the notification is received when the app is suspended or background and the notification launches the application I cannot seem to capture where the link is received and cannot follow the link, from what I can see that delegate method is not called?
below is the implementation for UNUserNotificationCenter(didReceive: withCompletionHandler:)
defer {
completionHandler()
}
guard
response.actionIdentifier == UNNotificationDefaultActionIdentifier ||
response.actionIdentifier == "open-dl" else {
return
}
guard
let url = response.notification.request.content.userInfo["link-to"] as? String,
let linkTo = URL(string: url) else {
return
}
if UIApplication.shared.applicationState == .background {
UserDefaults.standard.set(linkTo, forKey: "localdeeplink")
UserDefaults.standard.synchronize()
} else {
_ = UIApplication.shared.delegate?.application?(UIApplication.shared, open: linkTo, options: [:])
}
When I attempt to read that localdeeplink entry back out of UserDefaults it's empty.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
//add this function
if launchOptions != nil {
pushAction(launchOptions: launchOptions)
}
return true
}
func pushAction(launchOptions: [NSObject: AnyObject]?) {
//add code of UNUserNotificationCenter(didReceive: withCompletionHandler:) here
}
Add the code of UNUserNotificationCenter(didReceive: withCompletionHandler:) in pushAction function
When app is closed didRecive is not getting called, didFinish is called with the push payload.
Apple link go to Handling Remote Notifications at bottom

Getting access to EKCalendar

In my app, I have a switch that allows the user to put certain events in their agenda. I handle that as such:
#IBAction func putInAgenda(_ sender: UISwitch) {
let store = manager.store
if (sender.isOn){
store.requestAccess(to: EKEntityType.event, completion: {
(accessGranted: Bool, error: Error?) in
if accessGranted == true {
self.eventsHandler.importEventsInAgenda(id)
} else {
DispatchQueue.main.async {
sender.isOn = false
}
}
})
} else {
//
}
shared?.set(sender.isOn, forKey: "putInAgenda")
shared?.synchronize()
}
However, against my expectation, "store.requestAccess" not only requests, but also SETS.
As a result, when the user CANCELS the dialog, the switch switches back (expected) but any consecutive attempt to switch the switch to the ON position is honored with an OFF position, without a new dialog.
What should I do?
A privacy request is only ever asked once. If you detect that it is currently denied, you could either update the UI or prompt the user to go to Settings and turn it on. You can use UIApplication openSettingsURLString and UIApplication openURL to take the user to your app's settings page in the Settings app.

Urban Airship receivedBackgroundNotification never called

After reading all the guides, and after checking hundred of articles on the internet, I'm quite sure that the method receivedBackgroundNotification is never called.
Everything works perfect, but when the app is in background, a notification is shown and this method never is called. Seems to be impossible to get it working.
Assuming all the normal operations and the basic configuration is well done and is working, what can I do to intercept and manage background push notifications with this library?
I will appreciate a lot any help.
Make sure you have the following configured:
Remote notifications background mode enabled in the target's capabilities
Background app refreshed is enabled on your test device
Assuming you are trying to use the UAPushNotificationDelegate, make sure you either have automatic setup enabled or you are forwarding all the proper methods to UA SDK.
Apple will only wake up your application if you send the push notification with content-available=1 in the payload. The option is exposed in the composer as "background processing" or you can set it in the iOS overrides when using the push api.
In Urban Airship iOS SDK v.13.4.0 if func receivedBackgroundNotification(_ notificationContent: UANotificationContent, completionHandler: #escaping (UIBackgroundFetchResult) -> Void) is not called you can handle notifications in func receivedNotificationResponse(_ notificationResponse: UANotificationResponse, completionHandler: #escaping () -> Void) if you display notifications as alerts. Remember that you should only handle the notifications with actionIdentifier == UNNotificationDefaultActionIdentifier (the user opened the app from the notification interface).
This is an example of UAPushNotificationDelegate implementation:
extension MyPushNotificationDelegate: UAPushNotificationDelegate {
public func receivedForegroundNotification(_ notificationContent: UANotificationContent, completionHandler: #escaping () -> Void) {
completionHandler()
}
public func receivedBackgroundNotification(_ notificationContent: UANotificationContent, completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
completionHandler(.newData)
}
public func receivedNotificationResponse(_ notificationResponse: UANotificationResponse, completionHandler: #escaping () -> Void) {
guard notificationResponse.actionIdentifier == UNNotificationDefaultActionIdentifier else {
completionHandler()
return
}
someFunc() {
completionHandler()
}
}
public func extend(_ options: UNNotificationPresentationOptions = [], notification: UNNotification) -> UNNotificationPresentationOptions {
[.alert, .badge, .sound]
}
}

Firebase Notification - Firebase Cloud Messaging

In my application I'm using Firebase Messaging and I'm testing to receive notification.
I'm using Postman as Rest service to configure the notification's body like:
{
"to": "/topics/test",
"priority": "high",
"notification": {
"title": "Test",
"body": "New",
"badge": "0"
},
"data": {
"foo": "bar"
}
}
Certificate is ok. I don't understand how to start programmatically a ViewController looking at the data passed..For example if data contains:
"data": {
"foo": "viewcontroller1"
}
I'd like to start ViewController1 when user clicks on the notification.
I can only print data in AppDelegate? How can I use values passed?
This is my AppDelegate.swift:
import UIKit
import Firebase
import FirebaseMessaging
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
FIRApp.configure()
let notificationTypes : UIUserNotificationType = [UIUserNotificationType.Alert, UIUserNotificationType.Badge, UIUserNotificationType.Sound]
let notificationSettings = UIUserNotificationSettings(forTypes: notificationTypes, categories: nil)
application.registerForRemoteNotifications()
application.registerUserNotificationSettings(notificationSettings)
return true
}
// [START refresh_token]
func tokenRefreshNotification(notification: NSNotification) {
let refreshedToken = FIRInstanceID.instanceID().token()!
print("InstanceID token: \(refreshedToken)")
// Connect to FCM since connection may have failed when attempted before having a token.
connectToFcm()
}
// [START connect_to_fcm]
func connectToFcm() {
FIRMessaging.messaging().connectWithCompletion { (error) in
if (error != nil) {
print("Unable to connect with FCM. \(error)")
} else {
print("Connected to FCM.")
}
}
}
//Receive and handle messages
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
// Print message ID.
print("Value for foo -> \(userInfo["foo"])")
//start viewcontroller programmatically
}
func applicationWillResignActive(application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}
func applicationDidEnterBackground(application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(application: UIApplication) {
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
}
Can someone please explain me please?
Lets handle the code in didReceiveRemoteNotification First we extract which view controller should we present:
let type = userInfo["foo"] as! String
if type == "viewcontroller1" {
// here we go to start the view controller
}
You will need to use helping method to find the top most view controller to present on top of it.
func getTopViewController()->UIViewController{
if var topController = UIApplication.sharedApplication().keyWindow?.rootViewController {
while let presentedViewController = topController.presentedViewController {
topController = presentedViewController
}
return topController
// topController should now be your topmost view controller
}
return UIViewController()
}
To start a ViewController you should make an identifier for that in Storyboard. lets say its also called : viewcontroller1 then :
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("viewcontroller1") as! viewcontroller1
self.getTopViewController().presentViewController(vc, animated: true, completion: nil)
Note: When receiving the notification you'll need to check if the app was in background or it was in app or it was outside the app . For each one has different handling of how and when you'll need to show or present your view controller.