Shortcut/Action on Siri Watch Face with independent WatchOS app - swift

I'm relatively new to making apps, and even newer to independent watch apps. As training, I'm making a watch app that I can use to log my water intake throughout the day. I've created a new intent definition file (see image 1) on which I've checked the marks for all the target memberships (the app, the WatchKit app, and the WatchKit extension). Furthermore, the target membership class is a public intent for the WatchKit extension.
When logging my water I execute the following code:
let intent = INManager.intent(drink: item)
INManager.donateShortcuts(withIntent: intent)
and my IntentManager looks like this:
import Foundation
import Intents
class IntentManager {
func intent(drink: Drink) -> LogDrinkIntent {
let intent = LogDrinkIntent()
intent.uuid = drink.id.uuidString
intent.name = drink.name
intent.emoji = drink.emoji
return intent
}
func donateShortcuts(withIntent intent:INIntent) {
var relevantShortcuts: [INRelevantShortcut] = []
if let relevantShortcut = defaultRelevantShortcut(withIntent: intent) {
relevantShortcuts.append(relevantShortcut)
}
INRelevantShortcutStore.default.setRelevantShortcuts(relevantShortcuts) { (error) in
if let error = error {
print("Failed to set relevant shortcuts: \(error))")
} else {
print("Relevant shortcuts set.")
}
}
}
private func defaultRelevantShortcut(withIntent intent: INIntent) -> INRelevantShortcut? {
if let shortcut = INShortcut(intent: intent) {
let relevantShortcut = INRelevantShortcut(shortcut: shortcut)
relevantShortcut.shortcutRole = .action
let template = INDefaultCardTemplate(title: "Log Drink")
relevantShortcut.watchTemplate = template
print("Returning relevant shortcut.")
return relevantShortcut
}
return nil
}
}
When logging a drink the confirmation Returning relevant shortcut. and Relevant shortcuts set. are printed. However, the Siri watch face doesn't update to include a link to my action. I got the code for the IntentManager from this Medium article.
I really appreciate your time and help. I've had a hard time trying to find any details about this functionality and Apple's documentation is imo inferior. Thank you! If you need more details or such, feel free to ask.
Image 1

Let's saddle the horse from behind: Generally speaking, you want to make use of Soup Chef. Now you can categorize Siri suggestions into two sub-groups, being donated shortcuts and relevant shortcuts.
In your specific example of a "water intake" logging app, you should work with donating the INIntent to INIteraction. The reason for that is quite simple: Your suggestion is due to an action a user has committed within your application, not based upon plainly relevance, thus your passage about INRelevantShortcutStore isn't necessary and/or should be replaced with INInteraction.
To re-phrase myself: The issue is that you parse INRelevantShortcutStore as a donation, see here:
func donateShortcuts(withIntent intent:INIntent) {
var relevantShortcuts: [INRelevantShortcut] = []
if let relevantShortcut = defaultRelevantShortcut(withIntent: intent) {
relevantShortcuts.append(relevantShortcut)
}
INRelevantShortcutStore.default.setRelevantShortcuts(relevantShortcuts) { (error) in
if let error = error {
print("Failed to set relevant shortcuts: \(error))")
} else {
print("Relevant shortcuts set.")
}
}
}
... as explained above, that is not the correct usage for INIntent in your specific example.
I highly suggest to read through Soup Chef in general as well as specifically donating shortcuts to Siri (what you want to do!). The documentation is very detailed and explanative!

Related

How to request App Ratings with requestReview() correctly

I am currently learning how to ask the user for an AppStore rating in the app. I have read through numerous documentations and reports from Apple and other bloggers, but I still have questions:
I thought that I want to display my request after the user has completed a sequence of actions 3 times. But if the user doesn't want to rate my app right now, can I ask him to rate my app again later? I also read that Apple controls when and how often the review request is displayed (up to 3x / year)
My app has different functions. Therefore it is not guaranteed that the user accesses exactly this function, where I want to show the rating view. Is it therefore possible to call up the rating request at different points in the app and simply leave it up to Apple whether the rating request is also displayed?
Best regards
Edit for #K bakalov :
enum AppReviewRequest {
#AppStorage("runCountSinceLastRequest") static var runCountSinceLastRequest = 0
#AppStorage("lastVersion") static var lastVersion = ""
static let threshold = 4
static let currentVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as! String
static func countUpRequestReview() {
if currentVersion != lastVersion {
if runCountSinceLastRequest < threshold - 1 {
runCountSinceLastRequest += 1
print(runCountSinceLastRequest)
}
}
}
static func requestReviewIfNeeded() {
if currentVersion != lastVersion {
if runCountSinceLastRequest == threshold {
if let scene = UIApplication.shared.connectedScenes.first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene {
SKStoreReviewController.requestReview(in: scene)
lastVersion = currentVersion
runCountSinceLastRequest = 0
}
}
}
}
}
I assume you already read that but if you haven't I am leaving a link for you to check it out: https://developer.apple.com/documentation/storekit/requesting_app_store_reviews
It contains helpful information and guides when and how to prompt the user to leave a review.
Back to your questions.
Yes, you can ask again for review but there is no guarantee that the Review pop-up (alert) will be presented again. It is usually delayed in time and sometimes requires a new update of the app to trigger a new review prompt; That is correct, Apple has control over it but I don't know if its limited to only 3 times a year.
Yes, you can call it as many times as you want and wherever you find it suitable. Although, I highly encourage you to read (if still haven't) the link above. You need to think of a non-intrusive way to ask for review, otherwise you risk the user to dismiss the prompt, even if they like the app.
Just an advice, many apps use an approach with a custom popup "Do you like the app? Yes/No", if "Yes" then request a review using requestReview() call. And/or as mentioned in the article above, you can always use a manual review by redirecting to the AppStore, if you need it as a result of a CTA (tap on a button for example).

Possible to show users of your VSCode extension / color theme notifications on update?

Is it possible to show users of your extension or color theme notifications in Visual Studio Code? For someone who has my color theme or extension installed and is getting updates, I would like to possibly show this person a notification after they update the extension (That could be on launch of VSCode, or right after they go into the market to update & reload the extension and client themselves.)
For example: I think it would be beneficial to me and not invasive if they saw a notification after updating the extension saying "Feedback? Suggestions? Fixes?..on the theme?" OR notifying them of something changed in the theme that may not be favorable. So they can "opt out" of that change if they want (Like an extra set of borders around something or the color change of something.)
Obviously people with all notifications off would not be affected, but I thought an occasional notification after a rare update wouldn't be too bad. I have not been able to find info on if this is possible, and if it was, how to do it. Any info on this is appreciated. And if it is possible, those reading this, whether you've done it or not, would you recommend showing a notification to your theme users in that way?
Thanks :)
Show a notification on bottom-right corner, whenever your extension is updated. You can also control to show it only for major/minor releases.
That's how it looks:
Add below code to extension.ts:
import { window, ExtensionContext, extensions, env, Uri } from "vscode";
const extensionId = "jerrygoyal.shortcut-menu-bar";
// this method is called when your extension is activated
export function activate(context: ExtensionContext) {
showWhatsNew(context); // show notification in case of a major release i.e. 1.0.0 -> 2.0.0
}
// https://stackoverflow.com/a/66303259/3073272
function isMajorUpdate(previousVersion: string, currentVersion: string) {
// rain-check for malformed string
if (previousVersion.indexOf(".") === -1) {
return true;
}
//returns int array [1,1,1] i.e. [major,minor,patch]
var previousVerArr = previousVersion.split(".").map(Number);
var currentVerArr = currentVersion.split(".").map(Number);
if (currentVerArr[0] > previousVerArr[0]) {
return true;
} else {
return false;
}
}
async function showWhatsNew(context: ExtensionContext) {
const previousVersion = context.globalState.get<string>(extensionId);
const currentVersion = extensions.getExtension(extensionId)!.packageJSON
.version;
// store latest version
context.globalState.update(extensionId, currentVersion);
if (
previousVersion === undefined ||
isMajorUpdate(previousVersion, currentVersion)
) {
// show whats new notificatin:
const actions = [{ title: "See how" }];
const result = await window.showInformationMessage(
`Shortcut Menubar v${currentVersion} — Add your own buttons!`,
...actions
);
if (result !== null) {
if (result === actions[0]) {
await env.openExternal(
Uri.parse(
"https://github.com/GorvGoyl/Shortcut-Menu-Bar-VSCode-Extension#create-buttons-with-custom-commands"
)
);
}
}
}
}
You can see this implementation in my VSCode extension repo Shortcut Menu Bar
I think you can register the version during activation event and check for it on each activation. Then you can do whatever you want. For instance GitLens is migrating settings https://github.com/eamodio/vscode-gitlens/blob/master/src/extension.ts#L52 and i'm pretty sure I remember that they were opening a notification (but i have not found immediately in the code)
regards,

safari app extensions: broadcast a message to all tabs from swift background process

In a legacy extension it was possible to iterate over safari.application.activeBrowserWindow.tabs to send a message to all tabs registered with the extension.
Is there any equivalent available with the new safari app extensions?
I've been trough the docs but did not find any hints on how to achieve this very basic thing.
A horrible workaround would be to have all tabs ping the Swift background, but really this is such a basic thing it seems absurd that it is not available or covered by the docs, am I missing something?
I also tried keeping a weak map of all "page" instances as seen by "messageReceived" handler in the hope the SFSafariPage reference would be kept until a tab is closed but they are instead lost almost immediately, suggesting they are more message channels than actual Safari pages.
The way should be next:
in injected.js you send the message to your app-ext, e.g.
document.addEventListener("DOMContentLoaded", function (event) {
safari.extension.dispatchMessage('REGISTER_PAGE')
})
And in app-ext handle it with smth like this:
var pages: [SFSafariPage] = []
class SafariExtensionHandler: SFSafariExtensionHandler {
override func messageReceived(withName messageName: String, from page: SFSafariPage, userInfo: [String : Any]?) {
switch messageName {
case "REGISTER_PAGE":
if !pages.contains(page) {
pages.append(page)
}
default:
return
}
}
}
Well, then you can send the message to all opened pages during runtime by smth like this:
for p in pages {
p.dispatchMessageToScript(withName: "message name", userInfo: userInfo)
}
It looks hacky but yet workable. Enjoy :)

Changing app language on the fly in iOS

I am new to iOS and I know this is a very frequently asked question, but I have found no appropriate answer for my query.
I have added text to controls on the app like this
let productDescriptionCell = tableView.dequeueReusableCellWithIdentifier("textDescriptionCell") as! TextDescriptionCell
productDescriptionCell.labelForCell.text = "Description"
productDescriptionCell.labelForCell.sizeToFit()
productDescriptionCell.labelForCell.adjustsFontSizeToFitWidth = true
like the 'Description' text above.
I want to have a button in my app, which lets the user change the language on the fly. I have found the following code as the most relevant but this requires an app restart.
userDefaults.setObject(["\(cc)"], forKey: "AppleLanguages")
userDefaults.synchronize()
In addition, I have text hard coded in my storyboard controllers, I want those localized as well. How can I achieve this?
Thai and English are the languages I need localization for
productTitleCell.textFieldForCell.placeholder = SpecProvider.spec.localizedTuv(createLocalizedString("en", tuvEnglish: "Enter Title", tuvThai: "ป้อนชื่อ"))
func localizedTuv(localizedString: LocalizedString) -> String {
var locale = LocaleSelectionService.sharedInstance.getCachedLocale()
if locale.isEmpty {
locale = ""
}
switch(locale) {
case "en": return localizedString.getTuvEnglish()
case "th": return localizedString.getTuvThai()
default: return localizedString.getTuvEnglish()
}
}
I have tried the above code, but this could get very tedious. Please help, I am really stuck!
You have to create string files, and add the labels for every language you want to support. Once this is done you can use NSLocalizedString for the text used in your ViewController.
Here's a tutorial http://www.raywenderlich.com/64401/internationalization-tutorial-for-ios-2014

AppleWatch Speech-to-Text functionality not working

I am trying to implement Speech-to-text feature for watchkit app.
I referred this question which has sample code.
Following is the code I tried:
self.presentTextInputControllerWithSuggestions(["Start it", "Stop it"], allowedInputMode: .Plain, completion: { (selectedAnswers) -> Void in
if selectedAnswers.count > 0 {
if let spokenReply = selectedAnswers[0] as? String {
self.label.setText("\(spokenReply)")
}
}
})
label is a label to display text I speak.
When I run it, it shows the screen where you are supposed to speak (Siri kind of screen) and you have two options on top: ‘Cancel', and ‘Done'. Once I am done speaking, I tap on ‘Done’ but screen doesn’t go away or shows me initial screen, I always have to tap on ‘Cancel’ to go back, and I don’t get any speech data in form of text. I checked it and seems like selectedAnswers is always an empty array, unless I tap on the "Start it"/"Stop it" options.
Can anyone help me with this? I want to show the spoken message on label. I have code inside awakeWithContext method in InterfaceController.swift file, am I supposed to put it somewhere else?
I am using iPhone with iOS 9 beta 2 and watchOS 2 beta on AppleWatch.
Thanks!
You can ask for user input and give him suggestion (see Swift example bellow).
self.presentTextInputControllerWithSuggestions(["suggestion 1", "suggestion 2"] allowedInputMode: .Plain, completion: { (answers) -> Void in
if answers && answers.count > 0 {
if let answer = answers[0] as? String {
println("\answer")
}
}
})
If suggestion is nil it goes directly to dictation. It is not working on the simulator but it is on real watch.
Your approach is correct but something is wrong with your SIRI , try changing the language.
It should work like these.