I am using CloudKit+CoreData to store and sync and share my apps data. In my somewhat limited testing this is working quite well, but sync performance is unpredictable. Often it is very fast, sometimes it just stops until I put Mac app in background or restart. I guess from this thread this behavior is by design:
https://developer.apple.com/forums/thread/756315
I'm accepting that! :)
My plan is that I will use CloudKit+CoreData for source of truth syncing, but now I'm looking for an alternative channel that I can use to sync when multiple devices are working on this data at the same time. I think the basic design could be:
When a device starts editing a CKRecord
Post device IP and record ID to well known location/discovery service
Watch that location to see other devices that are editing that record
Make direct connection to those devices and sync through that connection (still also saving/merging with iCloud).
I think I know how to solve the data merge problems that will show up in this scenario, but I don't know what technologies I should use to create the sync channel. I'm looking for ideas. I don't want to run my own server.
SharePlay session seems almost perfect, but I'm not sure if it's really intended for this purpose. In particular I would want the session to start automatically (using participants from CKShare) without users having to manually join. Also I would like it to work with a single account (when I am viewing same data on my Mac and iOS device).
My other thought is that I would store active users+ip's in the synced CloudKit+CoreData store and then use Network.framework to connect and sync those active users. I think this could work, but is quite low level and might be a lot of work.
Are there other options that I'm missing or things I should think about?
Thanks,
Jesse
Post
Replies
Boosts
Views
Activity
I am trying to test CloudKit for sync, and am testing with the Xcode (15.4) SwiftUI templates (CoreData and SwiftData variants) and also with the CKSyncEngine sample-cloudkit-sync-engine example.
I am then testing sync by running app in my MacBook and my iPhone. Generally sync is working, but the behavior is very different when syncing changes from macOS -> iOS compared to from iOS -> macOS:
I open app on by my MacBook and iPhone
I add item on my MacBook, it shows up a second later on iPhone
On the other hand, if I add item on my iPhone it never shows up on my MacBook until I send app to background and then bring back to foreground.
Is this known/expected behavior? Any way to fix?
I assume this is a CKSyncEngine issue since the behavior is the same across all the different app setups and I think they all have CKSyncEngine in common.
For the sample-cloudkit-sync-engine example I have also tried calling try await self.database.syncEngine.fetchChanges(.init(scope: .all)) on the mac, but that doesn't seem to help. I still need to put app into background and then bring back to foreground before it will show changes.
I would expect changes to show up in my Mac UI without having to send app to background.
I'm using this build script:
https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution/customizing_the_notarization_workflow/customizing_the_xcode_archive_process?language=objc
To create and notarize a dmg. In Xcode 15 it fails when calling /usr/bin/hdiutil create .... The fail error message is:
could not access /Volumes/Bike/Bike.app - Operation not permittedhdiutil: create failed - Operation not permitted
I have found that I can work around the problem by giving Xcode 15 full disk access. Is there a way that I can make my script run without having to give Xcode full disk access?
Recently (I think when updated to Xcode 14.3) my macOS Shortcut actions
(implemented via AppIntent) started having a problem:
When I click an action's AppEntity parameter I see the following error instead of seeing a list of entries to select from.
The action “Demo Action” could not run because an internal error occurred.
One difference that I notice between my builds that didn't exhibit this error and my builds that do exhibit the error:
Working builds include objects.appintentsmanifest in the metadata folder
Builds that break with the above behavior do not include that file
Did something change in recent Xcode? What do I need to do so that Shortcuts app will run my queries again and provide popups so that I can select queried AppEnties?
To recreate the problem I created a new Document Based App in Xcode 14.3 and included the following code. I then dragged "Demo Action" into a new Shortcut and clicked "Show More" and then clicked "Books: Choose".
import AppIntents
struct BookEntity: Identifiable, AppEntity {
var id: UUID
@Property(title: "Title")
var title: String
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(title: LocalizedStringResource(stringLiteral: title))
}
init(id: UUID, title: String?) {
self.id = id
self.title = title ?? "Unknown Title"
}
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Book"
static var defaultQuery = BookQuery()
}
struct BookQuery: EntityStringQuery {
func entities(for identifiers: [UUID]) async throws -> [BookEntity] {
library.filter { identifiers.contains($0.id) }
}
func entities(matching query: String) async throws -> [BookEntity] {
library.filter { $0.title.localizedCaseInsensitiveContains(query) }
}
func suggestedEntities() async throws -> [BookEntity] {
library
}
}
struct DemoAction: AppIntent {
static var title: LocalizedStringResource = "Demo Action"
@Parameter(title: "Books")
var books: [BookEntity]
func perform() async throws -> some IntentResult {
.result()
}
}
let library: [BookEntity] = [
BookEntity(id: UUID(), title: "The Hobbit"),
BookEntity(id: UUID(), title: "The Lord of the Rings"),
]
I am trying to create an AppIntent that takes a rich text parameter. When I try the following it seems to work, but I get compiler errors because AttributedString doesn't conform to Sendable.
struct RichTextIntent: AppIntent {
static var title: LocalizedStringResource = "Rich Text"
@Parameter(title: "Text")
var richText: AttributedString
func perform() async throws -> some IntentResult {
.result()
}
}
Is there a way to accept a rich text parameter without compiler warnings?
The problem I am trying to solve is:
I have a macOS document based app
Each document contains a list of rows
I would like to have an AddRowIntent.
That intent should take a document as a parameter. I would also like an optional "after row" parameter. When specified the new row will be inserted after that existing row.
I want to use DynamicOptionProvider to generate a list of rows in the selected document to choose from, but I'm unsure how to do this. Is it possible? Or what should is the right design in this case, when an Intent has a container and and item in that container as parameters?
I am trying to implement text checking in my custom text editor by implementing NSTextCheckingClient, but I can't get anything to work. And I can't find much documentation or any example code anywhere.
I have created an example project here:
https://github.com/jessegrosjean/NSTextInputClient
I have a text cursor that's modeled as a CALayer. It "blinks" with an opacity animation.
I'm in process of moving most of my animations away from CAAnimation, and instead I am driving changes myself with a CVDisplayLink. Most of my animations are temporary, things like scroll the window, etc. And when I'm done I pause the CVDisplayLink.
My question: Is it reasonable to also drive the text cursor "blink" animation by my CVDisplayLink? That would mean my CVDisplayLink never gets to pause ... but it also means maybe the render server process does get to pause?
I have MyLayer and it has a sublayer that it also holds a reference to. In that setup what is the proper way to override init(layer: Any)?
Right now I'm doing it like this ... layer.mySublayer.presentation()! is that correct? Is that always going to work, or will presentation ever be nil in this context?
Thanks for any tips!
class MyLayer: CALayer {
var mySublayer: CALayer
public override init() {
mySublayer = CALayer()
super.init()
addSublayer(mySublayer)
}
override init(layer: Any) {
let layer = layer as! MyLayer
mySublayer = layer.mySublayer.presentation()!
super.init(layer: layer)
}
required init?(coder: NSCoder) {
fatalError("has not been implemented")
}
}
I have a model CALayer that I animate.
I then remove that layer from its super layer.
I then wait for a long while
After this I'm surprised to see that my model layer still has a non-nil presentation layer. Is this expected behavior?
If this is not expected behavior what might make my model layers presentation layer stick around like that?
Thanks
I am trying to use preference keys to mimic navigationBarTitle behavior. In particular I want a child preference key to bubble up and override one set by parent.
In this example I have defined a custom preference key and I set its value twice... right after also setting navigationBarTitle.
Notice that the navigationBarTitle value "Home 2" is overriding the "Home 1" value set in parent. I can't figure out how to also override the parent value of my custom preference key. No mater how I change my reducer function the final value is always the value set in parent, value == 1.
swift
struct MyPreferenceKey: PreferenceKey {
static var defaultValue: Int = 0
static func reduce(value: inout Int, nextValue: () - Int) {
value += nextValue()
}
}
struct MyPreferenceKeyView: View {
@State var preference: Int = 0
var body: some View {
VStack {
Text("\(preference)")
.navigationBarTitle("Home 2")
.preference(key: MyPreferenceKey.self, value: 2)
}
.navigationBarTitle("Home 1")
.preference(key: MyPreferenceKey.self, value: 1)
.onPreferenceChange(MyPreferenceKey.self) { value in
self.preference = value
}
}
}
I have a red and a green view.
When I "toggle cover" the red view should transition away off the bottom of the screen to reveal the green view.
When I "toggle cover" again the red view should transition up from the bottom of the screen to cover the green view.
I have this working in the below code, but to make it work I need to use weird scale: 0.9999 value. Is there a better way to do this?
I tried using .identity transition. I also tried using .scale(scale: 1.0), but in both cases the green view disappears early... can anyone tell me what's going on?
swift
import SwiftUI
struct TestCoverView: View {
@State private var showCover = true
var body: some View {
VStack {
Button("Toggle Cover") {
withAnimation {
showCover.toggle()
}
}
if (showCover) {
Color.red
.edgesIgnoringSafeArea(.all)
.transition(.move(edge: .bottom))
.zIndex(1)
} else {
Color.green
.edgesIgnoringSafeArea(.all)
.transition(.scale(scale: 0.9999))
}
}
}
}
I'm using JavascriptCore in a Swift app. For years I've had this logic:public func doit(_ options: [String : Any]?) {
jsValueOutline.invokeMethod("doit", withArguments: [options ?? [:]])
}In particular the logic to turn nil options into a dictionary like this `options ?? [:]`.Now this has started crashing for me. Seems maybe related to 10.15.4 ... though not entirely sure about that. Also is very odd in that I couldn't reproduce the problem for a few days... and now I can't not reproduce the problem. The crash stack looks like this:Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0 libobjc.A.dylib 0x00007fff6eb583c9 class_conformsToProtocol + 170
1 com.apple.JavaScriptCore 0x00007fff3858d6bd objectToValueWithoutCopy(JSContext*, objc_object*) + 77
2 com.apple.JavaScriptCore 0x00007fff3858cf3a objectToValue(JSContext*, objc_object*) + 74
3 com.apple.JavaScriptCore 0x00007fff3858ed17 -[JSValue invokeMethod:withArguments:] + 151Good news is I can work around the problem by chaning my above code to:publicfunc doit(_ options: [String : Any]?) {
jsValueOutline.invokeMethod("doit", withArguments: [options as Any])
}Note the change is to replace `options ?? [:]` with `options as Any`.My question... what happened. Was I don't something wrong initially and it just happened to work? What changed to cause it stop working. Is my "fix" correct?Thanks,Jesse