Post

Replies

Boosts

Views

Activity

Reply to DeviceActivityEvent with includesPastActivity = false, sometimes does not trigger callback
Hey, I'm experiencing similar issues with inconsistencies when includesPastActivity is false, the only (simple) solution I came up with was a version check, since then it works for above iOS 17.4 at least func activityEvent(_ key: String, threshold: DateComponents? = nil) -> DeviceActivityEvent { loadSelection(for: key) let applications = selectionToRestrict if #available(iOS 17.4, *) { return DeviceActivityEvent( applications: applications.applicationTokens, categories: applications.categoryTokens, webDomains: applications.webDomainTokens, threshold: threshold ?? DateComponents(minute: 0), includesPastActivity: true ) } else { return DeviceActivityEvent( applications: applications.applicationTokens, categories: applications.categoryTokens, webDomains: applications.webDomainTokens, threshold: threshold ?? DateComponents(minute: 0) ) } } If you don't want to set it to true, I unfortunately cannot help you. This seems to be one of many issues with DeviceActivity.
2w
Reply to Checking Equality of ApplicationToken Instances in Swift
Yes, using == to compare Tokens is reliable. When looking in the docs, you can also see why that is the case. All Tokens are defined as following: public typealias ActivityCategoryToken = Token<ActivityCategory> public typealias ApplicationToken = Token<Application> public typealias WebDomainToken = Token<WebDomain> where public struct Token<T> : Codable, Equatable, Hashable Therefore any Token you are comparing is Equatable. As you may know any struct that is implementing Equatable need to have the function static func == (lhs: Self, rhs: Self) -> Bool which is the reason why it must work. Regarding your second question Could there be issues with tokens stored in UserDefaults not matching due to reference or serialization differences? It depends on the implementation. If you're storing the tokens correctly inside the UserDefaults (using an AppGroup) there shouldn't be any problem, at least I myself haven't seen any. However when you're not using an AppGroup, the DeviceActivityMonitorExtension won't have access to the tokens due to it being sandboxed. Hope I could help you!
Sep ’24
Reply to Shielding .all(except: ) unexpected behavior
I have been able to solve this issue. It may vary for you, but mostly it will result to some value to be unexpectedly nil inside the ShieldConfiguration. TLDR You need to make sure that when overriding the ShieldConfiguration for Category (Application and Web) you don't use the WebDomainToken or ApplicationToken, but instead the CategoryToken. Any other token will result to nil (I think), which causes the ShieldConfiguration to crash and show the default screen. If this isn't your issue, maybe some other value will result to nil and cause your Configuration to crash. Here are the details: I have a function to create a ShieldConfiguration, which takes a token (the underlying Application, WebDomain or CategoryToken). I have created an enum for the tokens as following: public enum ShieldToken { case applicationToken(ApplicationToken) case webDomainToken(WebDomainToken) case categoryToken(ActivityCategoryToken) } Now inside the ShieldConfiguration when overriding the shielding for categories the issue was: override func configuration(shielding application: Application, in category: ActivityCategory) -> ShieldConfiguration { return customShieldConfiguration( token: .applicationToken(application.token!), // this was the issue: using the applicationToken when overriding the function for the category. Instead use .categoryToken(category.token!) displayName: application.localizedDisplayName) }
Sep ’24
Reply to Best way to pause DeviceActivitySchedule
I finally found a suitable solution, though it may not be the best approach. I'm storing the selection in shared UserDefaults with the Schedule ID as the key. This way, I can always retrieve the selected apps when I have the Schedule ID. This solved the problem of not having access to the apps that should be restricted. The other issue is actually pausing the monitoring, for which this solution is quite elegant: When the pausing action starts, all restrictions for this activity should be cleared, and the activity should no longer be monitored. Start a new activity with the identifier "pause+(oldID)" using the same selection and schedule times. The new schedule should contain a proper warningTime. Learn more about it here (it's not as simple as you might think): Apple Developer Documentation. In your DeviceActivityMonitor, your intervalDidStart(for activity:) function will now be called. But since the new schedule is started with the ID "pause+(oldID)", you can react to the name of the activity. You can now add logic like this: if !activity.rawValue.contains("pause+") { startMonitoring() } This will ensure that monitoring is not started when the ID contains "pause". In your DeviceActivityMonitor, override the function intervalWillEndWarning(for activity:) and add logic like this: override func intervalWillEndWarning(for activity: DeviceActivityName) { super.intervalWillEndWarning(for: activity) let activityName = activity.rawValue.contains("pause+") ? activity.rawValue.replacingOccurrences(of: "pause+", with: "") : activity.rawValue // Clears the old monitoring and store let center = DeviceActivityCenter() let store = ManagedSettingsStore(named: .init(activity.rawValue)) // Start new schedule when the pausing reaches its end time if let schedule = center.schedule(for: .init(rawValue: activity.rawValue)) { center.stopMonitoring([.init(activity.rawValue)]) store.clearAllSettings() // Start new monitoring startMonitoring() } } This way, you have successfully paused your schedule while maintaining the original begin and end times.
Aug ’24
Reply to AppIntent Parameter Argument List of Apps
I found the solution, with an EntityQuery you can achieve the expected result. The con is that you actually need to have a list of Apps for it to work. Here is the code: Step 1: Create an AppEntity struct EntityName: AppEntity, Identifiable { var id = UUID() let url: String let name: String var displayRepresentation: DisplayRepresentation { DisplayRepresentation(title: "\(name)") } static var typeDisplayRepresentation: TypeDisplayRepresentation = "App" static var defaultQuery = AppQuery() } Step 2: Create AppQuery struct AppQuery: EntityQuery, EntityStringQuery { func entities(for identifiers: [UUID]) async throws -> [EntityName] { identifiers.compactMap { id in apps.first { $0.id == id } } } func entities(matching string: String) async throws -> [EntityName] { return apps.filter { $0.name.localizedCaseInsensitiveContains(string) } } func suggestedEntities() async throws -> [EntityName] { // assuming you defined apps somewhere return apps } } Step 3: Use Entity in AppIntent struct SomeIntent: AppIntent { static let title: LocalizedStringResource = .init("T", defaultValue: "Some Name") static var openAppWhenRun: Bool = false @Parameter(title: "Select App Here!") var selectedApp: EntityName @MainActor func perform() async throws -> some IntentResult & OpensIntent { // handle } } Hope, this may help someone!
Apr ’24
Reply to missing com.apple.developer.family-controls / Missing Family Controls from provisioning profile
Since a lot of people are asking: Do I need to ask permissions for every app target? Yes, it seems like you need permissions for all your app targets. I've received approval for my app main target, but still got an error that the app couldn't be distributed (got the error 3 times, which is exactly the amount of targets my app has). After I've removed the targets, the app could be distributed. How long does it take to get a response? It depends, I myself didn't get an email response. You can check your access in the Main App Target < Signing & Capabilities. If there's Family Controls (Distribution) you have got access.
Feb ’24
Reply to Adding intent to the AppIntentsExtension
I've encountered the same issue before and found a solution. If you remove the extension and integrate your TWTAppIntent code directly into the main app target without the @main TWTAppIntentExtension, you should be able to resolve it. This is the approach I previously used and it worked effectively. However, if you prefer to continue using the AppIntent in the custom extension, I suspect the error may originate from the TWTAppIntentExtension itself. It appears to be an empty main-entry, but this is just speculation on my part.
Feb ’24
Reply to Set openAppWhenRun variable in AppIntent programatically.
It appears there's a solution to a problem, but I've yet to find it. The One-Sec App seems to utilize precisely what is described here. Currently, I've implemented the suggested solution, which technically works by throwing an error, but it's not ideal as it could be irritating for users to encounter it repeatedly. I've experimented with various methods, but nothing is working as intended: on one hand, it seems impossible to eliminate the error when attempting to use two different intents without throwing an error due to the mismatch in underlying types, and on the other hand, you can't catch the error to prevent the user from seeing it. It's clear there must be a solution to this problem, but I've been unable to discover it. Any guidance or insights would be greatly appreciated.
Feb ’24
Reply to Open parent app from ShieldAction extension in iOS
There should be a workaround, however I couldn't figure it out yet. The one-sec App for example is using exactly what is described here. It seems like the app launches itself through the shielding with a custom deeplink. I have tried opening my own app through the openURL property in the Environment and with NSExtensionContext. Here's what I've tried: Opening through Environment @Environment(\.openURL) var openURL override func handle(action: ShieldAction, for application: ApplicationToken, completionHandler: @escaping (ShieldActionResponse) -> Void) { // Handle the action as needed. switch action { case .primaryButtonPressed: openURL.callAsFunction(YourAppDeeplink) // other code } Opening with NSExtensionContext private func openApp(with url: URL) { let context = NSExtensionContext() context.open(url, completionHandler: nil) } However, neither approach has been successful in launching the app as intended. The one-sec App using this feature implies, that there must be a workaround or alternative method that we're missing.
Feb ’24
Reply to How to get BundleID from ApplicationToken?
I finally found a solution to your Problem. Instead of reading the AppIcon and AppName through the BundleID, you can retrieve it in a View using Label(selection.applicationTokens.first!).labelStyle(.titleAndIcon) You just need to get the applicationTokens and can retrieve the AppIcon and AppName through this Label. Hope this helps!
Feb ’24