Post

Replies

Boosts

Views

Activity

Reply to ForEach Breaks Data Binding
The ForEach View cannot not work with indicies for dynamic data, it requires identifiers which is why we supply it with an array of Identifiable data. This is so that it can calculate inserts, moves and deletions which obviously is impossible with an array of indicies which will in the case of moving 5 items around the indices will still be 0-4.
Feb ’22
Reply to SwiftUI: @StateObject never deinitialized
In SwiftUI we don't use view model objects. The View structs (or custom @State structs) hold the data that SwiftUI uses to diff and update the screen. View models are a UIKit pattern don't use it in SwiftUI, if you do then you'll add an unnecessary level of indirection and slow SwiftUI down and lose a lot of the magic behaviour.
Dec ’21
Reply to When to purge the persistent history?
Hi I'm not talking about purging all the history, only the history that is no longer needed because the NSCoreDataCoreSpotlightDelegate has already moved past it. As explained in the link I included (included again below), we have to purge the unneeded history otherwise we are wasting disk space. Purge History Because persistent history tracking transactions take up space on disk, determine a clean-up strategy to remove them when they > are no longer needed. Before pruning history, a single gatekeeper should ensure that your app and its clients have consumed the history they need. From Consuming Relevant Store Changes
Jun ’21
Reply to Notification/callback mechanism
There is info on how to do this in the docs here: https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingXPCServices.html#//apple_ref/doc/uid/10000172i-SW6-SW15 See the note that says: Note: If you want to allow the helper process to call methods on an object in your application, you must set the exportedInterface and exportedObject properties before calling resume. These properties are described further in the next section.
Jun ’21
Reply to Notification/callback mechanism
XPC is bidirectional. Just use setExportedInterface and setExportedObject in your client. Then in the server when you call a method on the remoteObjectProxy you will receive the method call in the exported object in the client. FYI this is how apps that use CoreLocation receive location updates from the locationd daemon. Use Hopper on the CoreLocation framework and take a look at _CLLocationManagerRoutineProxy createConnection for more detail. `/* @class _CLLocationManagerRoutineProxy */ -(void)createConnection { rbx = self; rdi = self->_connection; if (rdi != 0x0) { [rdi release]; *(rbx + 0x18) = 0x0; } rax = [NSXPCConnection alloc]; rax = [rax initWithMachServiceName:@"com.apple.locationd.routine" options:0x1000]; *(rbx + 0x18) = rax; if (rax != 0x0) { [*(rbx + 0x18) setExportedInterface:[NSXPCInterface interfaceWithProtocol:@protocol(CLLocationManagerRoutineClientInterface)]]; [*(rbx + 0x18) setExportedObject:rbx]; r14 = [*(rbx + 0x18) exportedInterface]; r13 = objc_opt_class(@class(NSArray)); objc_opt_class(@class(CLLocation)); [r14 setClasses:[NSSet setWithObjects:r13] forSelector:@selector(didUpdateLocations:) argumentIndex:0x0 ofReply:0x0]; rdx = [NSXPCInterface interfaceWithProtocol:@protocol(CLLocationManagerRoutineServerInterface)]; [*(rbx + 0x18) setRemoteObjectInterface:rdx]; r14 = [*(rbx + 0x18) serviceName]; rdi = *(rbx + 0x18); var_78 = *__NSConcreteStackBlock; *(&var_78 + 0x8) = 0xffffffffc2000000; *(&var_78 + 0x10) = sub_911b; *(&var_78 + 0x18) = 0x71d40; *(&var_78 + 0x20) = r14; [rdi setInterruptionHandler:rdx]; rdi = *(rbx + 0x18); var_50 = *__NSConcreteStackBlock; *(&var_50 + 0x8) = 0xffffffffc2000000; *(&var_50 + 0x10) = sub_9133; *(&var_50 + 0x18) = 0x71d40; *(&var_50 + 0x20) = r14; [rdi setInvalidationHandler:&var_50]; [*(rbx + 0x18) resume]; } if ([rbx updating] != 0x0) { [[[rbx connection] remoteObjectProxy] startUpdatingLocation]; } return; } `
Jun ’21
Reply to Shared App Groups cache not writable
Just wanted to share that I noticed a bug in the above old code sample, the second append was done on the wrong variable, here it is fixed and updated: guard let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: group) else { return } var cachesURL = url.appendingPathComponent("Library", isDirectory: true) cachesURL = cachesURL.appendingPathComponent("Caches", isDirectory:  false) Right now I'm struggling to get a disk URLCache to write to the group container, it fails with: 2021-05-24 18:46:55.879431+0100 URLCacheTestApp[95367:2519466] [logging-persist] cannot open file at line 44499 of [02c344acea] 2021-05-24 18:46:55.879495+0100 URLCacheTestApp[95367:2519466] [logging-persist] os_unix.c:44500: (0) open(/Users/me/Library/Group%20Containers/group.com.myco.MyApp/Library/Caches/Cache.db) - Undefined error: 0 2021-05-24 18:46:55.879567+0100 URLCacheTestApp[95367:2519466] NetworkStorageDB:_openDBReadConnections: failed to open read connection to DB @ /Users/me/Library/Group%20Containers/group.com.myco.MyApp/Library/Caches/Cache.db.  Error=14. Cause=unable to open database file
May ’21
Reply to List with Bindings from ObservedObject
Apple's Scrumdinger sample - https://developer.apple.com/tutorials/app-dev-training/getting-started-with-scrumdinger uses a function to generate a binding. In your case it would be like this: struct Todo: Identifiable {      let id = UUID()      var title: String      var isDone = false } class TodoStore: ObservableObject {     @Published var todos: [Todo] = [.init(title:"Test")] } struct ListRow: View {      @Binding var todo: Todo      var body: some View {           Button(action: {                self.todo.isDone.toggle()           }) {             Text("\(todo.title) \(todo.isDone.description)")           }      } } struct ContentView: View {      @StateObject var todoStore = TodoStore()      var body: some View {           List(todoStore.todos) { todo in                ListRow(todo: binding(for: todo))           }      } // from Scrumdinger sample app     private func binding(for todo: Todo) - BindingTodo {         guard let scrumIndex = todoStore.todos.firstIndex(where: { $0.id == todo.id }) else {             fatalError("Can't find scrum in array")         }         return $todoStore.todos[scrumIndex]     } }
Apr ’21
Reply to SwiftUI - Passing a variable to Observable Object
import SwiftUI import Firebase struct ItemsView: View { 	 var itemName: String 	 var itemID: String 	 @ObservedObject var itemsGetter = ItemsGetter() 		// ....etc .onAppear { 		itemsGetter.get(itemID:itemID) } 		} class ItemsGetter { 	 @Published var data = [items]() 	 func get(itemID:String) { 			let db = Firestore.firestore() 			db.collection("items").whreField("itemID", isEqualTo: itemID).addSnopshotListenser
Dec ’20
Reply to Does the new SwiftUI App Lifecycle support new windows on macOS/iPadOS?
Here is how to open a new window in SwiftUI on macOS. In your ContentView create a button and open a URL for your app and another View e.g. Viewer to be shown in the window we will open: struct ContentView: View { 		@Environment(\.openURL) var openURL 		var body: some View { 				VStack { 						Button("Open Viewer") { 								if let url = URL(string: "myappname://viewer") { 										 openURL(url) 								} 						} 						Text("Hello, world!") 				} 				.padding() 		} } struct Viewer: View { 		var body: some View { 				Text("Viewer") 		} } In your App add another WindowGroup for your viewer and set it to enable handling of external launch events (an internal event in our case). @main struct GroupDefaultsTestApp: App { 		var body: some Scene { 				WindowGroup { 						ContentView() 				} 				WindowGroup("Viewer") { // other scene 						Viewer() 				} 				.handlesExternalEvents(matching: Set(arrayLiteral: "*")) 		} } Now in Project->Info->URL Types type in myappname in the URL Schemes field (and the identifier field too) to register our app with the system. Now run your app and click the button and it should open a new window!
Dec ’20