Delve into the world of built-in app and system services available to developers. Discuss leveraging these services to enhance your app's functionality and user experience.

Post

Replies

Boosts

Views

Activity

EntityQuery defaultResult is not called again when new widgets are added. Shows stale data.
I'm converting my app to use the new AppIntent system in the widgets from the old custom intent based system. I'm trying to implement defaultResult in my EntityQuery, so the widget can be ready to go as soon as it's added to the home screen. https://developer.apple.com/documentation/widgetkit/making-a-configurable-widget https://developer.apple.com/documentation/appintents/uniqueappentityprovider/defaultresult() But the weird behavior is that defaultResult seems to be called once when the first widget is added to a home screen, and shows the correct data, but then the defaultResult method is never called again when subsequent widgets are added. It just uses the result from when the first widget was added. This especially causes issues because then a user will delete the item that the first widget was referencing, but adding new widgets still try to refer to the old AppEntity that no longer exists, even though "entities(for " returns nil for those IDs to signify that item no longer exists. I would have assumed defaultResult would get called every time a new widget is added, but haven't seen anyone else complain about this. Has anyone seen this issue before or have any advice?
3
1
240
Oct ’24
App Intents: Siri does not recognize currency amounts
Hello, I am implementing an App Intent which asks the user for a currency amount: private func loadAmountList(forNumber number: String) async throws -> [NSDecimalNumber] {...} @MainActor func perform() async throws -> some IntentResult & ShowsSnippetView { let list = try await loadAmountList(forNumber: fixedNumber).compactMap { currencyFormatter.string(from: $0) } throw $amount.needsDisambiguationError(among: list, dialog: "app_intent_sim_amount_prompt") } If I start this intent from Siri, the attached screenshot is shown, but no matter what I say ("10 EURO", "ten", "10", "10€"...) Siri never understands anything and keep reshowing the dialog over and over again. If instead I tap any of the choices then the intent execution proceeds currectly. How can I solve the problem? Thanks
1
0
168
4w
App Intents: requestConfirmation method not working with Siri invocation
Hello, I am implementing an App Intent which shows a confirmation dialog before proceeding with the operation execution. It works fine when the intent is started from a shortcut, but it always fails when started from Siri: I obtain the error message depicted in the attached screenshot ("An error occurred, try again"). That message appears as soon as the requestConfirmation method is called in the perform method of my App Intent: try await requestConfirmation(actionName: .do, dialog: "app_intent_sim_confirmation_message") { SIMRechargeIntentSummaryView(...) } ... How can I solve the problem? Thanks
0
0
145
4w
Device Activity Monitor
I'd like to block the apps selected in FamilyActivityPicker individually when a certain threshold is met. For example, let's say the threshold is 15 minutes, and I want to block both Photos and Freeform. If I spend 15 minutes on Photos, Photos should be blocked. Then, if I spend 15 minutes on Freeform, Freeform should also be blocked. Currently, only Photos gets blocked after 15 minutes, but Freeform does not. How can I fix this problem so that each app is blocked individually when its respective 15-minute threshold is met? Thank you in advance File 1 : class GlobalSelection { static let shared = GlobalSelection() var selection = FamilyActivitySelection() private init() {} } extension DeviceActivityName{ static let daily = Self("daily") } @objc(DeviceActivityMonitorModule) class DeviceActivityMonitorModule: NSObject { private let store = ManagedSettingsStore() @objc func startMonitoring(_ limitInMinutes: Int) { let schedule = DeviceActivitySchedule( intervalStart: DateComponents(hour: 0, minute: 0), intervalEnd: DateComponents(hour: 23, minute: 59), repeats: true ) let threshold = DateComponents(minute: limitInMinutes) var events: [DeviceActivityEvent.Name: DeviceActivityEvent] = [:] // Iterate over each selected application's token for token in GlobalSelection.shared.selection.applicationTokens { // Create a unique event name for each application let eventName = DeviceActivityEvent.Name("dailyLimitEvent_\(token)") // Create an event for this specific application let event = DeviceActivityEvent( applications: [token], // Single app token threshold: threshold ) // Add the event to the dictionary events[eventName] = event } // Register the monitor with the activity name and schedule do { try DeviceActivityCenter().startMonitoring(.daily, during: schedule, events: events) print("24/7 Monitoring started with time limit : \(limitInMinutes) m") } catch { print("Failed to start monitoring: \(error)") } } @objc static func requiresMainQueueSetup() -> Bool { return true } } FIle 2 : class DeviceActivityMonitorExtension: DeviceActivityMonitor { let store = ManagedSettingsStore() var blockedApps: Set<ApplicationToken> = [] func scheduleNotification(with title: String) { let center = UNUserNotificationCenter.current() center.requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in if granted { let content = UNMutableNotificationContent() content.title = "Notification" // Using the custom title here content.body = title content.sound = UNNotificationSound.default let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false) // 5 seconds from now let request = UNNotificationRequest(identifier: "MyNotification", content: content, trigger: trigger) center.add(request) { error in if let error = error { print("Error scheduling notification: \(error)") } } } else { print("Permission denied. \(error?.localizedDescription ?? "")") } } } // Function to retrieve selected apps func retrieveSelectedApps() -> FamilyActivitySelection? { if let sharedDefaults = UserDefaults(suiteName: "group.timelimit.com.zerodistract") { // Retrieve the encoded data if let data = sharedDefaults.data(forKey: "selectedAppsTimeLimit") { // Decode the data back into FamilyActivitySelection let decoder = JSONDecoder() if let selection = try? decoder.decode(FamilyActivitySelection.self, from: data) { return selection } } } return nil // Return nil if there was an error } override func intervalDidStart(for activity: DeviceActivityName){ super.intervalDidStart(for: activity) scheduleNotification(with: "Interval did start") scheduleNotification(with: "\(retrieveSelectedApps())") } override func intervalDidEnd(for activity: DeviceActivityName) { super.intervalDidEnd(for: activity) } override func eventDidReachThreshold(_ event: DeviceActivityEvent.Name, activity: DeviceActivityName) { super.eventDidReachThreshold(event, activity: activity) // Notify that the threshold is met scheduleNotification(with: "Threshold met") // Retrieve the selected apps if let selectedApps = retrieveSelectedApps() { // Extract the app token identifier from the event name let appTokenIdentifier = event.rawValue.replacingOccurrences(of: "dailyLimitEvent_", with: "") // Iterate over the selected application tokens for appToken in selectedApps.applicationTokens { // Convert the app token to a string representation (or use its debugDescription) let tokenString = "\(appToken)" // Check if the app token matches the token identifier in the event name if tokenString == appTokenIdentifier { blockedApps.insert(appToken) // Block only the app associated with this event store.shield.applications = blockedApps scheduleNotification(with: "store.shield.applications = blockedApps is reached") break } } } else { scheduleNotification(with: "No stored data for selectedAppsTimeLimit") } } override func intervalWillStartWarning(for activity: DeviceActivityName) { super.intervalWillStartWarning(for: activity) // Handle the warning before the interval starts. } override func intervalWillEndWarning(for activity: DeviceActivityName) { super.intervalWillEndWarning(for: activity) // Handle the warning before the interval ends. } override func eventWillReachThresholdWarning(_ event: DeviceActivityEvent.Name, activity: DeviceActivityName) { super.eventWillReachThresholdWarning(event, activity: activity) // Handle the warning before the event reaches its threshold. } }
0
0
177
4w
iOS universal links: excluded path opens in my app
Hello. Here is my AASA file (my appID changed): { "applinks": { "apps": [], "details": [ { "appIDs": [ "A123B4567C.app.myapp.tool" ], "components": [ { "/": "/en", "exclude": true }, { "/": "/en/*", "exclude": true }, { "/": "/workspace/*", "exclude": true }, { "/": "*" } ], "paths": [ "NOT /en", "NOT /en/*", "NOT /workspace/*", "*" ] } ] } } I need to open all links with my app except those with excluded flag. When I open 'right' links, my app opens them (that's great). When I open excluded link (e.g. https://myapp.app/workspace/personal) Safari opens it (that's great) but then the app is launched (that's what not expected). What I already checked: added the old "paths" property (it wasn't there originally) rebooted my device reinstalled my app from the TestFlight then from the AppStore asked ChatGPT used AASA validator (branch.io one) cleared Safari cache checked my links for redirects What else can I check? Thanks.
2
0
1.1k
Feb ’24
Supported URL Schemes
Some Apple URL schemes are documented for third-party use. It’s fine to use those URL schemes for their intended purpose. Other Apple URL schemes are not officially documented. Their use is unsupported. If you rely on such implementation details, things might work, or they might not, and that state might change over time. IMPORTANT If you ship via the App Store, pay attention to clause 2.5.1 of the App Review Guidelines. The Apple URL scheme documentation is not always easy to find. I’m aware of the following: Apple URL Scheme Reference QA1924 Opening Keyboard Settings from a Keyboard Extension [This Q&A was retired years ago.] Preparing your app to be the default messaging app The doc comments for es_new_client in <EndpointSecurity/ESClient.h> Developer > Bug Reporting describes the applefeedback scheme Additionally, as questions about this most commonly crop up in the context of opening Settings (System Settings on macOS), I wanted to highlight the following: UIApplication.openSettingsURLString property (in Objective-C this is UIApplicationOpenSettingsURLString) UIApplication.openNotificationSettingsURLString property (in Objective-C this is UIApplicationOpenNotificationSettingsURLString) AccessibilitySettings.openSettings(for:) method FIFinderSyncController.showExtensionManagementInterface() class method SMAppService.openSystemSettingsLoginItems() class method VSOpenTVProviderSettingsURLString global If your app needs to perform some action that’s not covered by the above, file an enhancement request for a supported way to do that. Make sure to describes your use case in detail. Share and Enjoy — Quinn “The Eskimo!” @ Developer Technical Support @ Apple let myEmail = "eskimo" + "1" + "@" + "apple.com" Revision History 2024-10-25 Added a link to UIApplication.openNotificationSettingsURLString and VSOpenTVProviderSettingsURLString. Added a link to Preparing your app to be the default messaging app. 2024-10-01 Added info about the applefeedback URL scheme. 2024-09-29 Added a link to SMAppService.openSystemSettingsLoginItems(). 2024-09-27 Added a titbit for Finder Sync extension developers. Added an invitation to file feedback. 2024-08-05 First posted.
0
0
421
Aug ’24
How to open parent app from `ShieldActionDelegate`
Hello, I think it is quite a common use-case to open the parent app that owns the ShieldActionDelegate when the user selects an action in the Shield. There are only three options available that we can do in response to an action: ShieldActionResponse.none ShieldActionResponse.close ShieldActionResponse.defer It would be great if this new one would be added as well: ShieldActionResponse.openParentApp While finding a workaround for now, the problem is that the ShieldActionDelegate is not a normal app extension. That means, normal tricks do not work to open the parent app from here. For example, UIApplication.shared.open(url) does not work because we can’t access UIApplication from the ShieldActionDelegate unfortunately. NSExtensionContext is also not available in the ShieldActionDelegate unfortunately, so that’s also not possible. There are apps however, that managed to find a workaround, in my research I stumbled across these two: https://apps.apple.com/de/app/applocker-passcode-lock-apps/id1132845904?l=en-GB https://apps.apple.com/us/app/app-lock/id6448239603 Please find a screen recording (gif) attached. Their workaround is 100% what I’m looking for, so there MUST be a way to do so that is compliant with the App Store guidelines (after all, the apps are available on the App Store!). I had documented my feature request more than 2 years ago in this radar as well: FB10393561
2
1
298
Oct ’24
ShieldConfiguration does not update when token is moved from one store to another (while app in foreground)
Hello fellow Screen Time Fans! I am encountering a strange problem since I started working with the Screen Time framework, and I don’t know what I’m doing wrong: Imagine the app has two ManagedSettingsStores: one to block apps during work hours (let’s say from 9am to 5pm) and one to block apps in the evening (let’s say from 5:30pm till midnight). Imagine, the user has blocked Instagram in both. When the user has Instagram open at 4:59pm it shows the Block during Work Hours Shield (so far, so good). At 5pm, the shield is removed, and the user can use Instagram. Then, at 5:30 the a shield is activated again: this time, the Instagram token is added to the evening store. However, there is no new ShieldConfiguration requested from the ShieldConfigurationDataSource. Instead, the previous shield from the work hour block is re-used and shown. To me, it appears that the Framework does not request new shields, when the token is moved from one store to another while the app remains in foreground. The Shield is only re-rendered when the user closes the shielded app and re-opens it. This is really confusing behavior and I would like to fix it. Did anyone here encounter something similar, and has a suggestion or workaround? My feedback is also documented in FB14237883.
1
0
211
Oct ’24
Setting tint for widget snapshot tests iOS 18
We have widget snapshot tests using XCTest as shown below: func testHomeWidgetView() { let widgetView = HomeWidgetView( state: .done, text: "All done!" ).frame(width: 170, height: 170) assertSnapshot(matching: widgetView, as: .image) } } Is it possible to apply a tint to the widgets in snapshot tests like a user can from their home screen in iOS 18? We'd like to capture snapshots of our widget while tinted via the home screen.
1
2
193
Oct ’24
Issue with Message Filter Extension service?
My server that backs my Message Filter Extension stopped receiving messages last night. I thought maybe I had broken something in the iOS code, even though I hadn't touched any of the logic related to filtering. So I rolled back my code to a previous version that was definitely working in both test and production and ran it on my test device, setting a breakpoint on the first line of the func handle(_ queryRequest: ILMessageFilterQueryRequest, context: ILMessageFilterExtensionContext, completion: @escaping (ILMessageFilterQueryResponse) -> Void) method in the extension. When sending a message to it from an unknown number, the breakpoint is never even hit. To ensure it was somehow not my code, I started a new blank app and added the Message Filter Extension target. Running it on my test device, it also doesn't ever hit the breakpoint. Is there some Apple service involved in determining whether to send unknown sender messages to Message Filter Extensions that might be down. Maybe it's a beta issue? I'm on iOS 18.1 Beta 4. But it seems odd that all of my users' devices would be encountering a beta-related issue at essentially the same time.
2
0
234
Oct ’24
AppDependcy in Widgets Extension
I am trying to get Controls working using AppIntents. My Intents make use of @Dependency, which get set up during application launch using AppDependencyManager.shared.add { ... }. When the app has been launched, my Controls work fine. However, when the app gets killed using the app switcher or by the system, my Controls cease to work. Checking in Console.app, I found the following: PROGRAMMING ERROR: Failed to retrieve dependency of type {my dependency}. Please register your dependency with AppDependencyManager before performing a dependent intent. My intents use openAppWhenRun = true, so I don't understand why the dependencies are not registered when running the Intent. Alternatively, I would like to know how to register AppDependencies in a Widgets Extension.
0
0
150
4w
App Clip card can be opened by tapping universal link or custom links?
Can the App Clip card be invoked by tapping on a link (not a default appclip link) which is shared through message box or whatsApp chat or Email? I gone through the reference of App Clip Card invocation from scanning QR code, App Clip Code, NFC Tag, Safari smart banner, default App Clip links and link presentation framework. But I didn't find any reference or documentation that custom links will invoke App Clip Card or won't invoke App Clip Card. So, need reference if possible that App Clip card be invoked by tapping on a custom link which is shared through whatsApp chat or message box or Email.
0
0
125
Oct ’24
"AVSpeechSynthesisVoice" choppy at start.
So, I'm trying to create my own text-to-speech setup. Problem I'm having is whenever I do a test run, the speech gets a bit choppy at the start kind of skipping over maybe a word or a few characters. A few details: I've essentially built a separate class for handling the speech events. AVSpeechSynthesizer is set up as a private variable for the class so I don't expect deallocation to be the issue. Especially since it's a problem at the start. I've got a queue set up for what it's worth so that shouldn't be a problem. I'd appreciate any advice.
2
0
164
Oct ’24
BadDeviceToken Error in Live Activities
Hello everyone, I’m currently receiving feedback from clients in a production environment who are encountering a BadDeviceToken error with Live Activities, which is preventing their states from updating. However, for other clients, the token is working fine and everything functions as expected. I’m collaborating with the back-end developers to gather more information about this issue, but the only log message we’re seeing is: Failed to send a push, APNS reported an error: BadDeviceToken I would greatly appreciate it if anyone could provide some insight or information on how to resolve this issue.
2
0
151
Oct ’24
Spotlight App shortcut can't refresh when refresh the AppEntity
I have encountered a problem about AppEntity and AppIntent shortcut. Here is what i have done. I created some shortcuts use AppShortcutsProvider. Each shortcut created by AppEntity. I search my app name in spotlight, so these shortcut can be shown in the spotlight. I don't click the shortcut and back to home screen. Then I delete these shortcuts by modify the Entity.defaultQuery and call AppShortcutsProvider.updateAppShortcutParameters() Going back to the spotlight, I can see my previous query and result. When I clicked the shortcut, I got an error WFBackgroundShortcutRunnerErrorDomain Should the spotlight result need to be refreshed when i come back to it?
2
0
226
Oct ’24
Siri not picking up app intent
Hello, I have written the following app intent and I can access it via shortcuts. But I can't get Siri to pick it up. I want it to have a dynamic book title (which could be anything) so that the user can say "Add (bookname) to my (app name). I need it to work for ios 17.1 onwards. I have added siri as a capability for my ios app. import AppIntents struct AddBookToReadingListIntent: AppIntent { static var title: LocalizedStringResource = "Add my Book" @Parameter(title: "Book Title", requestValueDialog: "What's the title of the book you want to add?") var bookTitle: String static var parameterSummary: some ParameterSummary { Summary("Add my '\(\.$bookTitle)'") } func perform() async throws -&gt; some IntentResult &amp; ReturnsValue&lt;String&gt; { return .result(value: "Added '\(bookTitle)' to your app") } } struct AppShortcuts: AppShortcutsProvider { static var appShortcuts: [AppShortcut] { AppShortcut( intent: AddBookToReadingListIntent(), phrases: [ "Add \(\.$bookTitle) in \(.applicationName)" ], shortTitle: "Add Book to app name", systemImageName: "book" ) } }
2
0
216
Oct ’24
App Intents is not able to start an outgoing call with CallKit when the app is backgrounded
I am currently working on integrating an app with Siri, adding support for starting VOIP calls and sending messages. Although it is understood it is recommended to use SiriKit for calling and messaging, I would like to allow users to select a profile to use for calling. As far as I am aware the notion of selecting a profile to call from is not something SiriKit supports, therefore, it was decided to go with App Intents to allow for more control over the parameters utilized to start calls. After integrating VOIP calling with App Intents, I noticed CallKit is not able to start calls when the App Intent is invoked from the background. I get the following error: Error Domain=com.apple.CallKit.error.requesttransaction Code=6 "(null)” This seems to correspond to the CXErrorCodeRequestTransactionError invalidAction. This error only happens when the intent is invoked from the background. Changing the App Intent property openAppWhenRun to true solves the issue as it brings the app to foreground before running the intent. However, I would like to support starting calls from the background to avoid making users unlock their phones prior to starting a call with Siri to make it a truly hands-free experience. I suspect the desired behavior is possible, most likely with SiriKit, as some famous VOIP calling apps (i.e. WhatsApp, Messenger, etc) exhibit the behavior I described. However, is there any way to start calls from the background with App Intents? Or is the desired behavior something exclusive to SiriKit? I have pasted three code snippets below that can replicate the issue. At the moment I am on Xcode Version 15.3, macOS Sonoma 14.6.1, and testing on iOS 16.6.1 To demonstrate the issue I have created the following CXProviderDelegate: class CallManager: NSObject, CXProviderDelegate { func startCall() { let callKitProvider = CXProvider(configuration: CXProviderConfiguration()) callKitProvider.setDelegate(self, queue: nil) let callKitController = CXCallController() let recipient = CXHandle(type: .generic, value: "Demo Outgoing Call") let uuid = UUID() let startCallAction = CXStartCallAction(call: uuid, handle: recipient) let transaction = CXTransaction(action: startCallAction) callKitController.request(transaction) { error in if let error { print(error) } else { print("no errors") } } callKitProvider.reportOutgoingCall(with: uuid, connectedAt: nil) } func providerDidReset(_ provider: CXProvider) { // no-op, not required to demonstrate the issue } } Then, I have a UIViewController that is the only screen of this example app: class ViewController: UIViewController { @IBOutlet weak var startCallButton: UIButton! override func viewDidLoad() { super.viewDidLoad() startCallButton.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside) } @objc func buttonTapped() { let manager = CallManager() manager.startCall() } } As for app intents, I put together a very simple intent to trigger the start of an outgoing call: struct StartCall: AppIntent { static var title: LocalizedStringResource = "Start Call" static var openAppWhenRun = false func perform() async throws -> some IntentResult { let manager = CallManager() manager.startCall() return .result() } } When the UIViewController is presented and I tap the button to start a call I see the green call banner appear and "no errors" is printed to the console as intended. However, when I open the Shortcuts app and run the app intent, the green banner does not appear and the message Error Domain=com.apple.CallKit.error.requesttransaction Code=6 "(null)” is printed to the console.
1
0
194
Oct ’24