SiriKit Media Intents - ios

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!!

Related

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 use TouchID in sirikit

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))
}

Initializer for conditional binding must have Optional type, not 'DynamicLinks'

Im getting this error while building iOS app.
Error showing on the line which I Bolded here
This is my code
#available(iOS 8.0, *)
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([Any]?) -> Void) -> Bool {
**guard let dynamicLinks = DynamicLinks.dynamicLinks() else {**
return false
}
let handled = dynamicLinks.handleUniversalLink(userActivity.webpageURL!) { (dynamiclink, error) in
self.openURL(url: userActivity.webpageURL!)
}
if !handled {
if let url = userActivity.webpageURL?.absoluteString {
self.openURL(url: URL(string: url)!)
}
}
return handled
}
Apparently DynamicLinks.dynamicLinks() doesn't yield an optional result. Change your code to this:
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([Any]?) -> Void) -> Bool {
let dl = DynamicLinks.dynamicLinks()
let handled = dl.handleUniversalLink(userActivity.webpageURL!) { (dynamiclink, error) in
self.openURL(url: userActivity.webpageURL!)
}
if !handled {
if let url = userActivity.webpageURL?.absoluteString {
self.openURL(url: URL(string: url)!)
}
}
return handled
}

Wait for function to end in Swift

I have a function to get data with json and i append all the data to an array. I try to create semaphore and wait until sending a signal to semaphore to continue but it doesn't work(I'm not sure if i do it correct or not), then i saw a question in Stackoverflow, answer was creating a completion handler like that
func application(application: UIApplication!, performFetchWithCompletionHandler completionHandler: ((UIBackgroundFetchResult) -> Void)!) {
getUrunGrup(completionHandler)
}
so i changed my function like that
func getUrunGrup(completionHandler: ((UIBackgroundFetchResult) -> Void)!){
Alamofire.request(.GET, "http://213.136.86.160:27701/Thunder/DataService/GetUrunGrup")
.responseJSON {(request, response, jsonObj, error) in
if let jsonresult:NSDictionary = jsonObj as? NSDictionary{
if let result: AnyObject = jsonresult["Result"] {
let elementCount = result.count
for (var i = 0; i<elementCount; ++i){
if let name: AnyObject = result[i]["Adi"]!{
if let kod:AnyObject = result[i]["Kod"]!{
urunUstGrup.append(["Adi": "\(name)", "Kod": "\(kod)"])
println("getUrunGrup \(i)")
}
}
}
}
}
}
completionHandler(UIBackgroundFetchResult.NewData)
println("Background Fetch Complete")
}
But there is no answer for how should i call this function?
you have to pass your async function the handler to call later on,like this:
func application(application: UIApplication!, performFetchWithCompletionHandler completionHandler: ((UIBackgroundFetchResult) -> Void)!) {
loadShows(completionHandler)
}
func loadShows(completionHandler: ((UIBackgroundFetchResult) -> Void)!) {
//....
//DO IT
//....
completionHandler(UIBackgroundFetchResult.NewData)
println("Background Fetch Complete")
}
OR (cleaner way IMHO)
add an intermediate completionHandler
func application(application: UIApplication!, performFetchWithCompletionHandler completionHandler: ((UIBackgroundFetchResult) -> Void)!) {
loadShows() {
completionHandler(UIBackgroundFetchResult.NewData)
println("Background Fetch Complete")
}
}
func loadShows(completionHandler: (() -> Void)!) {
//....
//DO IT
//....
completionHandler()
}

Resources