Howdy,
I'm trying to figure out how to replicate the following behavior for our app:
The system is able to ascertain that the Mac equivalent of some iOS app is installed locally, and it prevents notifications from being mirrored. However, I am unable to determine how this association is inferred. When I check our iOS app under this prefpane, the switch remains enabled and toggleable—we'd like to act like Slack here.
My initial assumption is that an app group containing both the Mac and iOS apps can be used to create the association; however, I would like to confirm that this is indeed the case before doing so. I'm not terribly confident about this.
Details:
The bundle identifiers of both apps do not match. This also applies to Slack; its iOS app is com.tinyspeck.chatlyio while its Mac app is com.tinyspeck.slackmacgap.
In our case, the iOS app's identifier is like com.company.app while the Mac app's identifier is com.company.app.desktop.
Both apps are signed with certificates that have matching team identifiers. The com.apple.developer.team-identifier entitlement is present on the Mac app.
The Mac app shares a keychain access group with the iOS app.
The Mac app is not sandboxed.
The Mac app is an Electron app.
The Mac app does not use APNs. It sends notifications "locally".
I currently only have the iOS app installed on my iPhone via TestFlight, if that matters.
Notification mirroring does work, but we'd like to forcibly disable this by associating the apps together.
To my knowledge, the iOS app makes use of both a UNNotificationServiceExtension and a UNNotificationContentExtension.
The iOS app currently doesn't have an assigned category (at least in Xcode). The Mac app is currently miscategorized as a developer tool (LSApplicationCategoryType = "public.app-category.developer-tools";), but that should be fixed.
(Redacted) bundle information for the Mac app:
CFBundleDisplayName = App;
CFBundleExecutable = "App Desktop";
CFBundleName = App;
Note that our CFBundleExecutable differs from the bundle's display name/name because we're currently migrating our users to a new version of the app that they'd likely want to live alongside the new one. The filename of the bundle itself is, similarly, App Desktop.app.
For the iOS app, to my knowledge, the CFBundleName and CFBundleDisplayName are App.
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
I'm trying out this code in a playground in Xcode 13.2 beta (13C5066c)
import _Concurrency
import Combine
import PlaygroundSupport
import Foundation
extension Task where Success == Never, Failure == Never {
static func sleep(seconds: Double) async throws {
try await Self.sleep(nanoseconds: UInt64(1e9 * seconds))
}
}
Task {
let values = PassthroughSubject<Int, Never>()
Task {
var counter = 0
while true {
counter += 1
print("[SEND] \(counter)")
values.send(counter)
try! await Task.sleep(seconds: Double.random(in: 0.1...0.5))
}
}
for await value in values
// vvvvvvvvvvvvv
.buffer(size: Int.max, prefetch: .keepFull, whenFull: .dropOldest)
// ^^^^^^^^^^^^^
.values {
print("[RECV] \(value)")
try! await Task.sleep(seconds: 1)
}
}
PlaygroundPage.current.needsIndefiniteExecution = true
This is modeled after real application code. For example, values could be a PassthroughSubject<Packet, NWError> (this is, in fact, what my app code looks like)
I've noticed that when doing Publisher.values to convert a Publisher into an AsyncPublisher (so we can use for await), the values aren't buffered.
In other words, if we are inside of the body of the for await loop and something is sent to the PassthroughSubject, that value is dropped unless we use .buffer beforehand.
This is demonstrated in the playground code above. The outer task receives values, but takes 1 second to process them. The inner task sends values at a fast rate (100ms–500ms). This means that values received during that 1 second period are dropped.(without the .buffer call; with that call, the problem goes away)
Is this intentional? I believe this should be more prevalent in documentation.
The documentation says that AsyncStream has a buffer:
An arbitrary source of elements can produce elements faster than they are consumed by a caller iterating over them. Because of this, AsyncStream defines a buffering behavior, allowing the stream to buffer a specific number of oldest or newest elements. By default, the buffer limit is Int.max, which means the value is unbounded.
But AsyncPublisher conforms to AsyncSequence, and not AsyncStream. Maybe this is how this can be fixed?
While not a major problem, selecting table rows seems to be noticeably more sluggish compared to other apps that make heavy use of table views (such as Finder).
In other words, the time between clicking and the table row visibly highlighting feels longer than in other apps.
I assume this is due to some details in SwiftUI that have yet to be sorted out, or is it the fault of the Garden App sample code? Perhaps both?
I'm trying to sequence two LongPressGestures, but my onEnded handler isn't being called when the second one finishes. Here is my code:
var timerGesture: some Gesture {
let cautionary = LongPressGesture(minimumDuration: 0.5)
.onEnded { _ in print("cautionary done") }
let lift = LongPressGesture(minimumDuration: .infinity)
.onEnded { _ in print("lift done") }
return cautionary.sequenced(before: lift)
}
This gesture manipulates a timer. The point of the gesture is to ensure that the user must long press for at least 0.5 seconds before they may lift their finger in order to start the timer. In other words, there is a 500ms long "confirmation" period where the user must hold down their finger before they can actually complete the gesture. Doing a simple tap will cancel the gesture during cautionary.
Here's what I'm trying to accomplish:
User long presses for at least 0.5 seconds. After 0.5 seconds, cautionary's onEnded is called.
The user releases their finger, and lift's onEnded is called.
However, lift's onEnded is never actually called. How can I achieve something like this using SwiftUI's gesture system?