How to use TouchID in sirikit - ios

Am exploring sirikit with touch ID in my app for payment. In the function handle i called the touch ID function but i'm getting blank black screen in siri UI can anyone help me if there is some way to do this.
func handle(intent: INSendPaymentIntent, completion: #escaping (INSendPaymentIntentResponse) -> Void) {
guard let payee = intent.payee, let amount = intent.currencyAmount else {
return completion(INSendPaymentIntentResponse(code: .unspecified, userActivity: nil))
}
LAContext().evaluatePolicy(.deviceOwnerAuthentication, localizedReason: "Allow siri to do the Payment", reply:
{(accessGranted:Bool, error:Error?) -> Void in
if (accessGranted){
print("Sending \(amount) payment to \(payee)!")
completion(INSendPaymentIntentResponse(code: .success, userActivity: nil))
}
})
print("Sending \(amount) payment to \(payee)!")
completion(INSendPaymentIntentResponse(code: .success, userActivity: nil))
}

Related

SiriKit Media Intents

I am trying to implement media intents(INPlayMediaIntentHandling), but nothing seems to work, the handlers are not being called nothing is executing, my aim is to capture what is being said to Siri ex: - Hey Siri, play Hero in MyApp
This is my intents handler -
class IntentHandler: INExtension, INPlayMediaIntentHandling {
func handle(intent: INPlayMediaIntent, completion: #escaping (INPlayMediaIntentResponse) -> Void) {
if let identifier = intent.mediaSearch?.mediaIdentifier {
print(identifier)
}
print("Aloha")
completion(INPlayMediaIntentResponse(code: .continueInApp, userActivity: nil))
}
func resolveMediaItems(for intent: INPlayMediaIntent, with completion: #escaping ([INPlayMediaMediaItemResolutionResult]) -> Void) {
if let identifier = intent.mediaSearch?.mediaIdentifier {
print(identifier)
}
print("Aloha")
completion([INPlayMediaMediaItemResolutionResult.unsupported()])
}
}
I do have this method in AppDelegete -
func application(_ application: UIApplication, handle intent: INIntent, completionHandler: #escaping (INIntentResponse) -> Void) {
guard let playMediaIntent = intent as? INPlayMediaIntent else {
completionHandler(INPlayMediaIntentResponse(code: .failure, userActivity: nil))
return
}
print("Print")
print(playMediaIntent.mediaSearch?.mediaIdentifier ?? "Print")
}
not sure what am doing wrong, any help is appreciated!!

How to debug remote push notification when app is not running and tap push notification?

When app is running and it receive push notification then didReceive is called.
func userNotificationCenter(
_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: #escaping () -> Void
)
So when above delegate is called then i present a screen using the payload i receive. There is no problem here.
When app is not running and user tap the notification then it should present the same screen like above. It's not working because i didn't added a code in didFinishLaunchingWithOptions.
So, then i added the following code -
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
......
if let userInfo = launchOptions?[UIApplication.LaunchOptionsKey.remoteNotification] as? [AnyHashable: Any] {
......
}
return true
}
But this is not working and i cannot debug because when in debug mode i have to kill the app from background and tap the notification but in this case the debugger won't work. I tried alternative method i.e. showing alert but then alert is also not working
let aps = remoteNotif["aps"] as? [AnyHashable: Any]
let string = "\n Custom: \(String(describing: aps))"
let string1 = "\n Custom: \(String(describing: remoteNotif))"
DispatchQueue.main.asyncAfter(deadline: .now() + 5) { [weak self] in
if var topController = application.windows.first?.rootViewController {
while let presentedViewController = topController.presentedViewController {
topController = presentedViewController
}
let ac = UIAlertController(title: string1, message: string, preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "OK", style: .default))
topController.present(ac, animated: true)
}
}
How should i solve this problem ?
“but in this case the debugger won't work” Not true! You can attach the debugger on launch even though Xcode did not launch it.
Edit the scheme, and in the Run action, under Info, where it says Launch, click the second radio button: “Wait for the executable to be launched.” Run the app; it doesn’t launch. Now launch the app through the push notification. The debugger works.
I have solved it by implementing sceneDelegate willConnectTo method. There is no need to handle it in didFinishLaunchingWithOptions
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
//Remote notification response
if let response = connectionOptions.notificationResponse{
print(response.notification.request.content.userInfo)
}
....
}
This is enough
You will have to create a NotificationServiceExtension and handle the payload there
In XCode,
Select your project
From bottom left select Add Target
Add Notification Service Extension
And then try doing something like this. The below code is for FCM but you can amend it according to your own payload.
For example -
import UserNotifications
import UIKit
class NotificationService: UNNotificationServiceExtension {
var contentHandler: ((UNNotificationContent) -> Void)?
var bestAttemptContent: UNMutableNotificationContent?
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: #escaping (UNNotificationContent) -> Void) {
self.contentHandler = contentHandler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
if let bestAttemptContent = bestAttemptContent {
// Modify the notification content here...
bestAttemptContent.title = "\(bestAttemptContent.title)"
guard let fcmOptions = bestAttemptContent.userInfo["fcm_options"] as? [String: Any] else {
contentHandler(bestAttemptContent)
return
}
guard let imageURLString = fcmOptions["image"] as? String else {
contentHandler(bestAttemptContent)
return
}
getMediaAttachment(for: imageURLString) { [weak self] (image, error) in
guard let self = self,
let image = image,
let fileURL = self.saveImageAttachment(image: image, forIdentifier: "attachment.png")
else {
// bestAttemptContent.body = "Error - \(String(describing: error))"
contentHandler(bestAttemptContent)
return
}
let imageAttachment = try? UNNotificationAttachment(
identifier: "image",
url: fileURL,
options: nil)
if let imageAttachment = imageAttachment {
bestAttemptContent.attachments = [imageAttachment]
}
contentHandler(bestAttemptContent)
}
}
}
override func serviceExtensionTimeWillExpire() {
// Called just before the extension will be terminated by the system.
// Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
contentHandler(bestAttemptContent)
}
}
private func saveImageAttachment(image: UIImage, forIdentifier identifier: String) -> URL? {
let tempDirectory = URL(fileURLWithPath: NSTemporaryDirectory())
let directoryPath = tempDirectory.appendingPathComponent(
ProcessInfo.processInfo.globallyUniqueString,
isDirectory: true)
do {
try FileManager.default.createDirectory(
at: directoryPath,
withIntermediateDirectories: true,
attributes: nil)
let fileURL = directoryPath.appendingPathComponent(identifier)
guard let imageData = image.pngData() else {
return nil
}
try imageData.write(to: fileURL)
return fileURL
} catch {
return nil
}
}
private func getMediaAttachment(for urlString: String, completion: #escaping (UIImage?, Error?) -> Void) {
guard let url = URL(string: urlString) else {
completion(nil, NotificationError.cannotParseURL)
return
}
ImageDownloader.shared.downloadImage(forURL: url) { (result) in
switch result {
case .success(let image):
completion(image, nil)
case .failure(let error):
completion(nil, error)
}
}
}
}
enum NotificationError: Error {
case cannotParseURL
}

iOS - AppDelegate not retrieving userActivity when Siri intent is launched

Actually making an app in swiftUI and trying to handle Siri intents to start a call in my app. I made a extension target in my project, added Siri capability and App groups in main and extension targets, my intent handler trigger the Siri request correctly at all, starting Siri and asking to make a call to some contact works perfectly, my apps gets launched and at this point is supposed to receive an activity from the intent, but nothing happen...
My intent handler looks like this:
import Intents
class IntentHandler: INExtension, INStartCallIntentHandling {
func handle(intent: INStartCallIntent, completion: #escaping (INStartCallIntentResponse) -> Void) {
let userActivity = NSUserActivity(activityType: NSStringFromClass(INStartCallIntent.self))
guard intent.contacts?.first?.personHandle?.value != nil else {
completion(INStartCallIntentResponse(code: .failureContactNotSupportedByApp, userActivity: userActivity))
return
}
let response = INStartCallIntentResponse(code: .continueInApp, userActivity: userActivity)
completion(response)
}
func resolveContacts(for intent: INStartCallIntent, with completion: #escaping ([INStartCallContactResolutionResult]) -> Void) {
var contactName = ""
if let contacts = intent.contacts {
contactName = contacts.first?.displayName ?? ""
}
//This shared method is used to get contact data for completion
DataManager.sharedManager.findContact(contactName: contactName, with: {
contacts in
switch contacts.count {
case 1: completion([.success(with: contacts.first ?? INPerson(personHandle: INPersonHandle(value: "1800-olakase", type: .phoneNumber), nameComponents: nil, displayName: "ola k ase", image: nil, contactIdentifier: nil, customIdentifier: INPersonHandle(value: "1800-olakase", type: .phoneNumber).value))])
case 2...Int.max: completion([.disambiguation(with: contacts)])
default: completion([.unsupported()])
}
})
}
func confirm(intent: INStartCallIntent, completion: #escaping (INStartCallIntentResponse) -> Void) {
let userActivity = NSUserActivity(activityType: NSStringFromClass(INStartCallIntent.self))
let response = INStartCallIntentResponse(code: .ready, userActivity: userActivity)
completion(response)
}
}
Added INStartCallIntent to IntentsSupported in Info.plist
So, when the code in func handle(intent: INStartCallIntent, completion: #escaping (INStartCallIntentResponse) -> Void) did complete is supposed to send the NSUserActivity to my app delegate directly to func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([UIUserActivityRestoring]?) -> Void) -> Bool ... but is not triggering that function. At the moment my app can't make new calls because is not getting any userActivity to retrieve contact data.
Also I try with func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([Any]?) -> Void) -> Bool but is not working too.
To being more specific I follow this tutorial but changing the
INStartAudioCallIntent for INStartCallIntent because is deprecated. I don't know if swiftUI is the problem or not, I used #UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate to add my app delegate to the swiftUI life cicle
And yes, my app has request Siri authorization and enabled. Any suggestion? I'm missing something?
Ok, I figured it out: The problem was that I was trying to get the NSUserActivity from the method func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([UIUserActivityRestoring]?) -> Void) -> Bool from my AppDelegate that never gets triggered, forget about that and don't follow the documentation from apple if you are using SwiftUI because is not working anymore.
For SwiftUI you must implement .onContinueUserActivity() modifier to fetch the NSUserActivity, and from here you can do whatever you need to do. Example code:
WindowGroup {
ContentView().onContinueUserActivity(NSStringFromClass(INStartCallIntent.self), perform: { userActivity in
//do something
})
}

how to access push notification response without tapping on banner or before showing notification?

I'm implemented push notification in my app in this way
//MARK:- Register notifications
func registerForPushNotifications() {
if #available(iOS 10.0, *){
let center = UNUserNotificationCenter.current()
center.delegate = self
center.requestAuthorization(options:[.badge, .alert, .sound]) { (granted, error) in
if (granted)
{
UIApplication.shared.registerForRemoteNotifications()
}
else{
//Do stuff if unsuccessful...
}
// Enable or disable features based on authorization.
}
}
else
{
//If user is not on iOS 10 use the old methods we've been using
let types: UIUserNotificationType = [UIUserNotificationType.badge, UIUserNotificationType.alert, UIUserNotificationType.sound]
let settings: UIUserNotificationSettings = UIUserNotificationSettings( types: types, categories: nil )
UIApplication.shared.registerUserNotificationSettings( settings )
UIApplication.shared.registerForRemoteNotifications()
}
}
//MARK: Push Notifications Delegate Methods
func application( _ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data ) {
var token = ""
for i in 0..<deviceToken.count {
//token += String(format: "%02.2hhx", arguments: [chars[i]])
token = token + String(format: "%02.2hhx", arguments: [deviceToken[i]])
}
USER_DEFAULTS.setValue(token, forKey: "Device_ID")
USER_DEFAULTS.synchronize()
}
func application( _ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error ) {
print( error.localizedDescription )
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) {
UIApplication.shared.applicationIconBadgeNumber = 0
alertRemoteNotification(userInfo as NSDictionary)
}
//Code for showing alert when in foreground
func alertRemoteNotification(_ userInfo : NSDictionary)
{
if UIApplication.shared.applicationState == .active {
if let aps = userInfo as? NSDictionary {
if let apsDidt = aps.value(forKey: "aps") as? NSDictionary {
if let alertDict = apsDidt.value(forKey: "alert") as? NSDictionary {
if let notification_type = alertDict.value(forKey: "name") as? String {
if let notification_Message = alertDict.value(forKey: "body") as? String {
let alert = UIAlertController(title: notification_type.capitalized + " Alert", message: notification_Message, preferredStyle: UIAlertControllerStyle.alert)
let okayBtn = UIAlertAction(title: "OK", style: .default, handler: { (action) -> Void in
// When Okay
UIApplication.shared.applicationIconBadgeNumber = 0
if #available(iOS 10.0, *) {
let center = UNUserNotificationCenter.current()
center.removeAllDeliveredNotifications() // To remove all delivered notifications
center.removeAllPendingNotificationRequests()
} else {
// Fallback on earlier versions
UIApplication.shared.cancelAllLocalNotifications()
}
let rootViewController = self.window!.rootViewController as! UINavigationController
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let dashBoardVC = mainStoryboard.instantiateViewController(withIdentifier: "DashBoardVC") as! DashBoardVC
rootViewController.pushViewController(dashBoardVC, animated: false)
})
let cancelBtn = UIAlertAction(title: "Cancel", style: .default, handler: { (action) -> Void in
UIApplication.shared.applicationIconBadgeNumber = 0
if #available(iOS 10.0, *) {
let center = UNUserNotificationCenter.current()
center.removeAllDeliveredNotifications() // To remove all delivered notifications
center.removeAllPendingNotificationRequests()
} else {
// Fallback on earlier versions
UIApplication.shared.cancelAllLocalNotifications()
}
})
alert.addAction(okayBtn)
alert.addAction(cancelBtn)
self.window?.rootViewController!.present(alert, animated: true, completion: nil)
}
}
}
}
}
}
else {
let rootViewController = self.window!.rootViewController as! UINavigationController
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let dashBoardVC = mainStoryboard.instantiateViewController(withIdentifier: "DashBoardVC") as! DashBoardVC
rootViewController.pushViewController(dashBoardVC, animated: false)
}
}
//Delegate methods
#available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler([.sound, .alert, .badge])
UIApplication.shared.applicationIconBadgeNumber = 0
}
#available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
let userInfo = response.notification.request.content.userInfo as NSDictionary
completionHandler()
self.alertRemoteNotification(userInfo as NSDictionary)
}
I could able to access the responce after tapping notification banner but the actual issue is when i'm in foreground i need to show an alert with the notification responce without tapping on notification banner. Please let me know how can get responce without tapping on notification banner.
iOS 10+ Provides delegate userNotificationCenter:willPresentNotification:withCompletionHandler
Asks the delegate how to handle a notification that arrived while the
app was running in the foreground.
And this will call only if app opened.
Also you can use CONTENT-AVAILABLE=1 for triggering methods.
FLOW:(Without Taping Notification, content-available:1)
App Opened State:- willPresentNotification(ios10+) -> didReceiveRemoteNotification:fetchCompletionHandler
App in Background:- didReceiveRemoteNotification:fetchCompletionHandler
App Closed:- You won't get notification data unless, the app opened by clicking Notification
Alternate method: Using Rich Notification
You can use Notification Extensions to create custom push notifications(contents including images/videos). Notification Service Extension & Notification Content Extension used to achieve this. mutable-content:1 required to trigger this. Here you can download images, get data, etc. [But the data can be shared with App ONLY through UserDefaults(App Groups), correct me if i'm wrong]
You could search for some random tutorials
Create notification service extension to process notification data. you will get 30seconds to process pushnotification via notification service extension
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: #escaping (UNNotificationContent) -> Void) {
if let copy = request.content.mutableCopy() as? UNMutableNotificationContent {
// Process your notification here
contentHandler(copy)
}
}
Hope this will help you
#available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler([.sound, .alert, .badge])
UIApplication.shared.applicationIconBadgeNumber = 0
// Added This line
alertRemoteNotification(notification.request.content.userInfo as NSDictionary)
}
Working without any issues.

iOS - aSyncAfter while app is in background

I'm attempting to run a a simple iOS application that pushes a notification to a user's screen after a specified time.
So far, this is what I have (borrowed from another thread):
DispatchQueue.global(qos: .background).async {
print( "background task" )
DispatchQueue.main.asyncAfter( deadline: .now() + milliseconds( 2000 )) {
let content = UNMutableNotificationContent()
content.body = "Testing :)"
content.badge = 1
let trigger = UNTimeIntervalNotificationTrigger( timeInterval: 2, repeats: false )
let request = UNNotificationRequest( identifier: "test", content: content, trigger: trigger )
UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
print( "background finish" )
}
}
My only issue is that the aSync After doesn't run whenever the app is in the background.
For example, if a user goes into their lockscreen or a different app, the notification never gets triggered.
Would anyone have a suggestion for how I could achieve this?
Thank you! :)
Approach:
Use UNNotificationRequest with time interval
Below mentioned solution would work in the following scenarios:
Foreground
Background
App is closed
Steps:
Set the delegate (to be alerted in foreground)
Request authorisation from user to be alerted
Create the notification
Add it to the notification center
AppDelegate:
AppDelegate must conform to UNUserNotificationCenterDelegate.
Set the notification center's delegate to the AppDelegate
import UserNotifications
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
UNUserNotificationCenter.current().delegate = self
return true
}
//MARK: UNUserNotificationCenterDelegate
//This is required to be alerted when app is in foreground
func userNotificationCenter(_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
print("will present")
completionHandler([.alert, .badge, .sound])
}
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: #escaping () -> Void) {
print("did receive")
}
}
Setting up notification:
import UserNotifications
private func setupNotification() {
requestAuthorization { [weak self] isGranted, error in
if let error = error {
print("Request Authorization Error: \(error)")
return
}
guard isGranted else {
print("Authorization Denied")
return
}
self?.addNotification()
}
}
private func requestAuthorization(completionBlock: #escaping (Bool, Error?) -> ()) {
let center = UNUserNotificationCenter.current()
center.requestAuthorization(options: [.alert, .badge, .sound]) { isGranted, error in
completionBlock(isGranted, error)
}
}
private func addNotification() {
let content = UNMutableNotificationContent()
content.title = "Testing Notification"
content.body = "This is a test for notifications"
content.sound = .default()
let timeInterval = TimeInterval(5)
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: timeInterval, repeats: false)
let request = UNNotificationRequest(identifier: "Something",
content: content,
trigger: trigger)
let center = UNUserNotificationCenter.current()
center.add(request) { error in
if let error = error {
print("Error adding notification request: \(error)")
}
else {
print("Successfully added notification request")
}
}
}

Resources