iOS ShareContext tapping on Suggestion Intent property of extensionContext is nil - ios

I have a ShareExtension in my iOS app. I am trying to use Suggestions. I can successfully 'donate' the intent using the following code from the apple developer website:
let groupName = INSpeakableString(spokenPhrase: "Juan Chavez")
let sendMessageIntent = INSendMessageIntent(recipients: nil,
content: nil,
speakableGroupName: groupName,
conversationIdentifier: "sampleConversationIdentifier",
serviceName: nil,
sender: nil)
// Add the user's avatar to the intent.
let image = INImage(named: "Juan Chavez")
sendMessageIntent.setImage(image, forParameterNamed: \.speakableGroupName)
// Donate the intent.
let interaction = INInteraction(intent: sendMessageIntent, response: nil)
interaction.donate(completion: { error in
if error != nil {
// Add error handling here.
} else {
// Do something, e.g. send the content to a contact.
}
})
This works fine and I am able to see my app icon in the suggestion row on the top for each conversation. However when I click on the suggestion, the intent property of the extentsionContext is nil:
override func viewDidLoad() {
super.viewDidLoad()
// Populate the recipient property with the metadata in case the user tapped a suggestion from the share sheet.
let intent = self.extensionContext?.intent as? INSendMessageIntent
if intent != nil { // this is nil despite selecting suggestion
let conversationIdentifier = intent!.conversationIdentifier
self.recipient = recipient(identifier: conversationIdentifier!)
}
}
My ShareExtension plist is as follows:
The other odd behaviour is that I'm able to do the donate from the main app but not from the app extension. In the main app the only relevant entry in the plist is the same NSUserActivityTypes entry. Not the NSExtension entries.
My understanding is that tapping on the suggestion, the extensionContext should contain the intent.

Related

Supporting Share Sheet Suggestions

Following this article I'm trying to implement share sheet suggestions in my messaging app.
I have declared INSendMessageIntent support in my Share extension's info.plist.
It's even showing up in the target under 'Supported Intents'.
But every time I'm donating a INSendMessageIntent it fails saying INSendMessageIntent is not supported.
Error Domain=IntentsErrorDomain Code=1901 "Donating intent 'INSendMessageIntent' is not supported by this extension. Please make sure that you declare the intents that your app supports by including the NSUserActivityTypes key in its Info.plist or your app contains an Intents extension that supports this intent."
// Create an INSendMessageIntent to donate an intent for a conversation with Juan Chavez.
let groupName = INSpeakableString(spokenPhrase: "Juan Chavez")
let sendMessageIntent = INSendMessageIntent(recipients: nil,
content: nil,
speakableGroupName: groupName,
conversationIdentifier: "sampleConversationIdentifier",
serviceName: nil,
sender: nil)
// Add the user's avatar to the intent.
let image = INImage(named: "Juan Chavez")
sendMessageIntent.setImage(image, forParameterNamed: \.speakableGroupName)
// Donate the intent.
let interaction = INInteraction(intent: sendMessageIntent, response: nil)
interaction.donate(completion: { error in
if error != nil {
// Add error handling here.
} else {
// Do something, e.g. send the content to a contact.
}
})
You should add NSUserActivityTypes entry to your app's Info.plist. Declare it as array and add your intents to it.

Is it possible to change NSUserActivity's userInfo from another app?

I am trying to open app B, from app A using universal links as below:
#IBAction func openButtonPressed(_ sender: Any) {
if let appURL = URL(string: "https://website.com/section/Paris") {
UIApplication.shared.open(appURL) { success in
if success {
print("The URL was delivered successfully.")
} else {
print("The URL failed to open.")
}
}
} else {
print("Invalid URL specified.")
}
}
In App B's AppDelegate -> application continueUserActivity restorationHandler method, it calls another function where the userActivity is processed.
Here there is a check for NSUserActivity's userInfo.
It is checking for a key PageName in NSUserActivity's userInfo property, and according to the value it assigns a type to the activity and routes the app accordingly.
NSString *page = activity.userInfo[#"PageName"];
if ([page isEqualToString:#"Places"]) {
return UserActivityTypePlaces;
} else {
return UserActivityTypeLink;
}
I was wondering if I can change or add a key value pair to userInfo in App A, so that App B can route me to another tab. (In this case I want it to open another tab and search the location string that is in the universal link)
I have checked other questions related with NSUserActivity's userInfo on StackOverflow and Apple Developer forums but they are not changing it on the calling app.(such as in App A)
Thank you
I made these in App A:
let activity = NSUserActivity(activityType: NSUserActivityTypeBrowsingWeb)
activity.title = "Places"
activity.userInfo = ["Page" : "Places"]
let userInfoSet:Set = ["Page"]
activity.requiredUserInfoKeys = userInfoSet
self.userActivity = activity
self.userActivity?.becomeCurrent()
and added the NSUserActivityDelegate method:
override func updateUserActivityState(_ activity: NSUserActivity) {
let dict:Dictionary = ["Page":"Places"]
userActivity?.addUserInfoEntries(from: dict)
}
But the NSUserActivity object is not something that goes from one app to the other. So this change only works within app A.
Hope this helps someone else too.

Facing issue in Siri Integration with custom intents

I’m trying to integrate Siri Shortcuts to my application. The concept which I’m trying is to get reward points of my card with secret pin confirmation. Please find what I have done for this below.
Enabled Siri in capabilities and added Siri Intent definition file.
Added new custom intent named say Rewards.
Defined the title. Subtitle and params(accType, pin) with confirmation enabled. Pin will be sent separately to user.
Then defined the intent response with param ‘rewardPoints’ and defined the response messages.
Added Siri intent extensions.
Added custom intent to info.plist files within project and intent extension.
Verified and added new handler for the custom intent and define the resolve, handle and confirm methods as below. For now, I’m returning random no for reward points.
//
// RewardsIntentHandler.swift
// SiriIntentExt
//
import UIKit
import Intents
class RewardsIntentHandler: NSObject, RewardsIntentHandling {
func resolveAccType(for intent:RewardsIntent, with completion: #escaping ([INStringResolutionResult]) -> Void) {
guard let accType = intent.accType else {
completion([INStringResolutionResult.needsValue()])
return
}
completion([INStringResolutionResult.success(with: accType)])
}
func resolvePin(for intent:RewardsIntent, with completion: #escaping ([INIntegerResolutionResult]) -> Void) {
guard let verifyPin = intent.pin else {
completion([INIntegerResolutionResult.needsValue()])
return
}
completion([INIntegerResolutionResult.confirmationRequired(with: verifyPin as? Int)])
}
func confirm(intent: RewardsIntent, completion: #escaping (RewardsIntentResponse) -> Void) {
completion(RewardsIntentResponse.init(code: RewardsIntentResponseCode.ready, userActivity: nil))
}
func handle(intent: RewardsIntent, completion: #escaping (RewardsIntentResponse) -> Void) {
guard intent.accType != nil else {
completion(RewardsIntentResponse.init(code: RewardsIntentResponseCode.continueInApp, userActivity: nil))
return
}
guard intent.pin != nil else {
completion(RewardsIntentResponse.init(code: RewardsIntentResponseCode.continueInApp, userActivity: nil))
return
}
let response = RewardsIntentResponse.success(rewardPoints: NSNumber(value: 3453))
completion(response)
}
}
Modified the IntentHandler to return rewards handler for rewards intent
//
// IntentHandler.swift
// SiriIntentExt
//
import Intents
class IntentHandler: INExtension {
override func handler(for intent: INIntent) -> Any {
if intent is RewardsIntent {
return RewardsIntentHandler()
}
return self
}
}
Donated the intent on view load as below.
//
// ViewController.swift
// Shortcuts
//
import UIKit
import Intents
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
siriAuthorisarion()
donateRewardIntent()
}
func siriAuthorisarion() {
INPreferences.requestSiriAuthorization { (status) in
print("Siri Authorization Status - ", status)
}
}
func donateRewardIntent() {
let rewardsIntent = RewardsIntent()
rewardsIntent.suggestedInvocationPhrase = "Reward Points"
rewardsIntent.accType = "test account"
let interaction = INInteraction(intent: rewardsIntent, response: nil)
interaction.donate { error in
if let error = error {
print("Donating intent failed with error \(error)")
}
DispatchQueue.main.async {
let alert = UIAlertController.init(title: ((error != nil) ? "Error" : "Success"), message: ((error != nil) ? "Oops!!! Error occured on donating intent." : "Intent donated succussfully!!!"), preferredStyle: .alert)
alert.addAction(UIAlertAction.init(title: "OK", style: .default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}
}
}
I'm facing problem from the above code base. Siri is not requesting for pin and not able to get the exact reward points for the account.
Have following questions.
Can we add the intents programmatically to Siri instead adding from shortcuts app or settings. So that user can directly use the functionality once installing the application.
Once intent is added using Shortcuts app, I’m trying the ask Siri for reward points. Its immediately requesting for my app shortcuts defined. Once we say 'yes' to request, I need to be asked for pin. But Siri replies with some problem with my app. What to be done for asking for next param value.
In the handler file, I have added the resolve methods for each parameters. I feel, resolve methods are not getting called to validate the values. Do we need to handle anything to make resolve methods work?
How can I debug the handler implementation using breakpoint within resolve/handle/confirm methods.
Thanks in advance.
Find my analysis for the above questions.
Can we add the intents programmatically to Siri instead adding from shortcuts app or settings. So that user can directly use the functionality once installing the application.
By default, intents are provided for specific domains such as messaging, payments, photos, workout, etc. No need to explicitly add intents through shortcuts for theses specific domains. Apart from these domains if we are creating custom intent, we are in need to donate and add the intents to Siri using shortcut/settings application.
Once intent is added using Shortcuts app, I’m trying the ask Siri for reward points. Its immediately requesting for my app shortcuts defined. Once we say 'yes' to request, I need to be asked for pin. But Siri replies with some problem with my app. What to be done for asking for next param value.
From iOS13, Apple has added Siri parameters and Siri suggestion for custom intents to request the missing parameters. Till iOS12, we don't have parameters option for custom intents.
In the handler file, I have added the resolve methods for each parameters. I feel, resolve methods are not getting called to validate the values. Do we need to handle anything to make resolve methods work?
In iOS12, we cannot add resolve methods for parameters in custom intents. Resolve methods handled only for specific domains provided within Intents extensions as mentioned in question 1. From iOS13, we can have resolve methods for custom intents based on the parameters.
How can I debug the handler implementation using breakpoint within resolve/handle/confirm methods.
We can add breakpoints and debug intent handler methods.
Thanks.

CFNotificationCenter repeating events statements

I have been working on an enterprise iOS/Swift (iOS 11.3, Xcode 9.3.1) app in which I want to be notified if the screen changes (goes blank or becomes active) and capture the events in a Realm databse. I am using the answer from tbaranes in detect screen unlock events in IOS Swift and it works, but I find added repeats as the screen goes blank and becomes active:
Initial Blank: a single event recorded
Initial Re-activiation: two events are recorded
Second Blank: two events are recorded
Second Re-act: three events are recorded
and this cycle of adding an additional event recording each cycle.
This must be something in the code (or missing from the code) that is causing an additive effect but I can’t find it. And, yes, the print statements show the issue is not within the Realm database, but are actual repeated statements.
My code is below. Any suggestions are appreciated.
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), //center
Unmanaged.passUnretained(self).toOpaque(), // observer
displayStatusChangedCallback, // callback
"com.apple.springboard.hasBlankedScreen" as CFString, // event name
nil, // object
.deliverImmediately)
}
private let displayStatusChangedCallback: CFNotificationCallback = { _, cfObserver, cfName, _, _ in
guard let lockState = cfName?.rawValue as String? else {
return
}
let catcher = Unmanaged<AppDelegate>.fromOpaque(UnsafeRawPointer(OpaquePointer(cfObserver)!)).takeUnretainedValue()
catcher.displayStatusChanged(lockState)
print("how many times?")
}
private func displayStatusChanged(_ lockState: String) {
// the "com.apple.springboard.lockcomplete" notification will always come after the "com.apple.springboard.lockstate" notification
print("Darwin notification NAME = \(lockState)")
if lockState == "com.apple.springboard.hasBlankedScreen" {
print("A single Blank Screen")
let statusString = dbSource() // Realm database
statusString.infoString = "blanked screen"
print("statusString: \(statusString)")
statusString.save()
return
}

Open URL from button action in Swift

In my app I am retrieving an URL from a parse.com Object.
I want to open the device's browser and show the URL received from Parse when the user clicks on the button.
This is the action:
#IBAction func botonNuevos(sender: AnyObject) {
let query = PFQuery(className: "datos_contacto")
query.getObjectInBackgroundWithId("H52ZxGm3U8", block: {
(questionObject: PFObject?, error: NSError?) -> Void in
let webNuevos: AnyObject! = questionObject!.valueForKey("dato_contacto")
print(webNuevos) //= http://www.touchemotors.com
if let webCallURL = NSURL(string: webNuevos as! String ) {
let application = UIApplication.sharedApplication()
if application.canOpenURL(webCallURL) {
application.openURL(webCallURL)
}
else{
print("failed")
}
}
})
}
when the button is clicked, the log shows the received URL, but the browser is not launched and any error is shown.
Please tell what is wrong in my code. Thank you
UPDATE
Finally after discussion with OP it turned out that value returned by questionObject!.objectForKey("dato_contacto") had whitespace at the end so NSUrl(string:) did not parse it well.
UPDATE
You are using wrong method NSObject.valueForKey(). Use PFObject.objectForKey() instead.
For iOS 9:
From here:
If your app is linked on or after iOS 9.0, you must declare the URL
schemes you want to pass to this method. Do this by using the
LSApplicationQueriesSchemes array in your Xcode project’s Info.plist
file. For each URL scheme you want your app to use with this method,
add it as a string in this array.
If your (iOS 9.0 or later) app calls this method using a scheme you
have not declared, the method returns false, whether or not an
appropriate app for the scheme is installed on the device.
Add http scheme to LSApplicationQueriesSchemes array in Info.plist.

Resources