Post

Replies

Boosts

Views

Activity

SwiftUI ReferenceFileDocument and Document-scoped bookmarks
Context I'm writing an app that allows a user to select folders, and then scan them for files. The folder could be on the user's machine, iCloud or external storage. The app persists the details of these files and folders to a document, so the user can access the files or re-scan the folders in the future. The App is being written using the SwiftUI framework for MacOS and the iPad. Problems Given this is a Sandboxed app I need to create security-scoped bookmarks to be able to access the files and folders that have been persisted to the document. I have two questions: How can I create a document-scoped bookmark, when using ReferenceFileDocument protocol. I need the document's URL, but I will never have access to this as I'm using the ReferenceFileDocument. I want to achieve something like this:   func fileWrapper(snapshot: MyDocument, configuration: WriteConfiguration) throws - FileWrapper { : let bookmarkDataToPersist = snapshort.sourceFolderURL.bookmarkData(options: .withSecurityScope, includingResourceValuesForKeys: nil, relativeTo: documentURL) : } 2. Ideally the user would be able to: * connect an external drive to their Mac * select a folder on that drive * save the document, which would persist a bookmark to that folder's URL * send the document to an iPad (via email or iCloud drive) * open the document using the iPad version of the App * connect the external bookmark to the iPad * re-scan the folder which was book marked in the document from the folder But given the problem in 1) and the fact that document-bookmarks cannot point to folders, is there a way? Any ideas or suggestions would be very welcome
0
0
464
Apr ’21
Suppressing verbose console output - SwiftUI / MacOS Monterey
Is there anyway to suppress the very verbose console output I am getting with MacOS Monterey and SwiftUI code? Here's a snippet of the output: 2021-07-20 09:58:18.267602+0100 BBCFTEST[19341:1880374] IMKInputSession (deactivate) CFRunLoopObserver kCFRunLoopExit (State change BEGIN) - innerRunLoopCount=1, otherInnerRunLoopDetected=0 2021-07-20 09:58:18.267637+0100 BBCFTEST[19341:1880374] IMKInputSession (deactivate) CFRunLoopObserver kCFRunLoopExit (State change END) - innerRunLoopCount=0, otherInnerRunLoopDetected=0 2021-07-20 09:58:18.270063+0100 BBCFTEST[19341:1880374] IMKInputSession (deactivate) CFRunLoopRunInMode exited, (kCFRunLoopRunStopped) 2021-07-20 09:58:18.270109+0100 BBCFTEST[19341:1880374] IMKInputSession (deactivate) CFRunLoopRunInMode (kIMKXPCPrivateRunLoopMode) ] RunLoopFinished(1)/Stopped(2) - Run result = 2, (Invocation already done = 1) (Sentinel IsZombie = 0) 2021-07-20 09:58:18.270147+0100 BBCFTEST[19341:1880374] IMKInputSession (deactivate) CFRunLoopRunInMode() LOOP DONE!
7
0
1.8k
Jul ’21
Monterey / SwiftUI view not updating as expecting
The following code is stripped out from an app that compiles and works under MacOS 11. It shows a tab bar - clicking on an item will highlight it. Under the tab bar is a text edit field where one can edit the title of the selected tab. There are 2 issues: The tab title shown in the title editor is allows out of phase from the selected tab Irrespective of which tab item is selected, when the title is edited it always updates the first tab I'm not sure if I was very lucky that this code ever worked under Big Sur, or if there is an issue with Monterey. I'm definitely not holding this up as example code - I'm sure there are better ways to implement it, but seek opinions on whether it should work. import SwiftUI class Document: ObservableObject { var tabs = [Tab(id: 0), Tab(id: 1)] var viewSettings = ViewSettings() } class Tab: ObservableObject, Identifiable { let id: Int @Published var title: String init(id: Int) { self.id = id self.title = "Tab \(id)" } } class ViewSettings: ObservableObject { @Published var activeTab: Int = 0 } @main struct Test: App { @StateObject var document: Document = Document() var body: some Scene { WindowGroup { ContentView() .padding() .environmentObject(document) .environmentObject(document.viewSettings) } } } struct ContentView: View { @EnvironmentObject var document: Document @EnvironmentObject var viewSettings: ViewSettings var body: some View { TabBar(viewSettings: document.viewSettings) TabEditView(activeTab: document.tabs[viewSettings.activeTab]) } } struct TabEditView: View { @EnvironmentObject var viewSettings: ViewSettings @ObservedObject var activeTab: Tab @State var title: String = "" init(activeTab: Tab) { print("CONSOLE - Init TabEditView for tab \(activeTab.id)") self.activeTab = activeTab } var body: some View { HStack { Text("Tab title:") TextField("Tab title:", text: $title, onCommit: { activeTab.title = title }) .onAppear { title = activeTab.title } .onChange(of: viewSettings.activeTab) { _ in print("CONSOLE - Updating TextField from tab \(activeTab.id)") title = activeTab.title } } } } struct TabBar: View { @EnvironmentObject var document: Document @ObservedObject var viewSettings: ViewSettings var body: some View { HStack { ForEach(document.tabs, content: TabItem.init) } } } struct TabItem: View { @EnvironmentObject var viewSettings: ViewSettings @ObservedObject var tab: Tab init(_ tab : Tab) { self.tab = tab } var body: some View { Text(tab.title) .padding(2) .background(tab.id == viewSettings.activeTab ? .red : .clear) .cornerRadius(4) .onTapGesture { viewSettings.activeTab = tab.id } } }
0
0
514
Jul ’21
Xcode 13 beta 3 - should it create Localizable.strings file for the base languge?
I wanted to know if I am doing something fundamentally wrong with Xcode 13 beta 3, with respect to localisation. I'm using the new compiler support to extract strings for localisation then exporting -> translating -> importing via the .xcloc files. All this works nicely, aside from one problem... I'm having difficulty getting it to create the initial Localizable.strings file in the development language, i.e. en in my case. When I try and import the relevant en.xcloc file nothing happens. I can import other languages without problem. Here are the steps I am taking: Write some basic code with one piece of text that can be localised Select Product -> Export Localisations... and choose English - Development Language Correctly creates en.xcloc file - contents as expected, picking up the text to be localised. Select Product -> Import Localisations... and re-import the english en.xcloc file This does nothing. So I try the following: Go to Project's file -> Info tab and add a new language, say (Fr) Select Product -> Export Localisations... and choose English - Development Language and French options Open fr.xcloc file and add translations required Come back to Xcode and import fr.xcloc file Now the Localizable.strings file appears - it is of course the French version Importing the en.xcloc file still does nothing If I open up the file inspector on the right, I can click to add an English version, but it simply copies the French version (which causes lots of other issues)... Any ideas?
1
0
816
Jul ’21
Accessing an actor's isolated state from within a SwiftUI view
I'm trying to understand a design pattern for accessing the isolated state held in an actor type from within a SwiftUI view. Take this naive code: actor Model: ObservableObject { @Published var num: Int = 0 func updateNumber(_ newNum: Int) { self.num = newNum } } struct ContentView: View { @StateObject var model = Model() var body: some View { Text("\(model.num)") // <-- Compiler error: Actor-isolated property 'num' can not be referenced from the main actor Button("Update number") { Task.detached() { await model.updateNumber(1) } } } } Understandably I get the compiler error Actor-isolated property 'num' can not be referenced from the main actor when I try and access the isolated value. Yet I can't understand how to display this data in a view. I wonder if I need a ViewModel that observes the actor, and updates itself on the main thread, but get compile time error Actor-isolated property '$num' can not be referenced from a non-isolated context. class ViewModel: ObservableObject { let model: Model @Published var num: Int let cancellable: AnyCancellable init() { let model = Model() self.model = model self.num = 0 self.cancellable = model.$num // <-- compile time error `Actor-isolated property '$num' can not be referenced from a non-isolated context` .receive(on: DispatchQueue.main) .sink { self.num = $0 } } } Secondly, imagine if this code did compile, then I would get another error when clicking the button that the interface is not being updated on the main thread...again I'm not sure how to effect this from within the actor?
2
2
7.8k
Aug ’21
Conforming @MainActor class to Codable
How does one add Codable conformance to a class that needs to be isolated to the MainActor? For example, the following code gives compiler errors: @MainActor final class MyClass: Codable { var value: Int enum CodingKeys: String, CodingKey { case value } init(from decoder: Decoder) throws { // <-- Compiler error: Initializer 'init(from:)' isolated to global actor 'MainActor' can not satisfy corresponding requirement from protocol 'Decodable' let data = try decoder.container(keyedBy: CodingKeys.self) self.value = try data.decode(Int.self, forKey: .value) } func encode(to encoder: Encoder) throws { // <-- Compiler error: Instance method 'encode(to:)' isolated to global actor 'MainActor' can not satisfy corresponding requirement from protocol 'Encodable' var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(value, forKey: .value) } } I'm definitely struggling to get my head around actors and @MainActor at the moment!
1
1
2.6k
Aug ’21
Conforming an actor to Sequence protocol
How can one conform an actor to the Sequence protocol? The following code generates the compiler warning: Instance method 'makeIterator()' isolated to global actor 'MainActor' can not satisfy corresponding requirement from protocol 'Sequence'. @MainActor class Test: Sequence { private var contents: [Int] = [] func makeIterator() -> Array<Int>.Iterator { contents.makeIterator() } }
0
0
764
Aug ’21
SwiftUI macOS document app architecture in a concurrent world
I'm trying to figure out the correct structure for a macOS document app using SwiftUI and Swift 5.5 concurrency features. I want to demonstrate updating a document's data asynchronously, in a thread safe manner, with the ability to read / write the data to a file, also thread-safe and in the background. Yet I am struggling to: write clean code - some of it looks inelegant at best, more like clunky, compared to my prior apps which used DispatchQueues etc implement Codeable conformance for an actor I'm seeking ideas, corrections and advice on how to improve on this. I've posted the full code over at GitHub, as I will only highlight some particular elements here. This is a minimum viable app, just for proof-of-concept purposes. The app The app displays a list of Records with a button to add more. It should be able to save and reload the list from a file. Current approach / design I've chosen the ReferenceFileDocument protocol for the Document type, as this is what I would use in a future app which has a more complex data structure. (i.e. I'm not planning on using a pure set of structs to hold a documents' data) Document has a property content of type RecordsModelView representing the top-level data structure. RecordsModelView is annotated with @MainActor to ensure any updates it receives will be processed on the main thread. RecordsModelView has a property of type RecordsModel. This is an actor ensuring read/write of its array of Records are thread safe, but not coordinated via the MainActor for efficiency. The app assumes that the func to add an item takes a long time, and hence runs it from with a Task. Although not demonstrated here, I am also making the assumption that addRecord maybe called from multiple background threads, so needs to be thread safe, hence the use of an actor. The code compiles and runs allowing new items to be added to the list but... Issues Firstly, I can't annotate Document with @MainActor - generates compiler errors I cannot resolve. If I could I think it might solve some of my issues... Secondly, I therefore have a clunky way for Document to initialise its content property (which also has to be optional to make it work). This looks nasty, and has the knock on effect of needing to unwrap it everywhere it is referenced: final class Document: ReferenceFileDocument { @Published var content: RecordsViewModel? init() { Task { await MainActor.run { self.content = RecordsViewModel() } } } // Other code here } Finally, I can't get the RecordsModel to conform to Encodable. I've tried making encode(to encoder: Encoder) async, but this does not resolve the issue. At present, therefore RecordsModel is just conformed to Decodable. func encode(to encoder: Encoder) async throws { // <-- Actor-isolated instance method 'encode(to:)' cannot be used to satisfy a protocol requirement var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(records, forKey: .records) }
1
0
1.8k
Aug ’21
MainActor.run failing to run closure when called from within a detached task
I have encountered an issue when trying to update the status of a detached task, by passing a closure to MainActor.run. To illustrate the issue consider a function that counts the number of files in a folder and its sub-directories. It runs in a Task.detached closure, as I don't want it blocking the main thread. Every 10,000th file it updates a Published property fileCount, by passing a closure to MainThread.run. However, the UI is failing to update and even giving me a spinning beach ball. The only way to stop this is by inserting await Task.sleep(1_000_000_000) before the call to MainThread.run. Here's the code: final class NewFileCounter: ObservableObject { @Published var fileCount = 0 func findImagesInFolder(_ folderURL: URL) { let fileManager = FileManager.default Task.detached { var foundFileCount = 0 let options = FileManager.DirectoryEnumerationOptions(arrayLiteral: [.skipsHiddenFiles, .skipsPackageDescendants]) if let enumerator = fileManager.enumerator(at: folderURL, includingPropertiesForKeys: [], options: options) { while let _ = enumerator.nextObject() as? URL { foundFileCount += 1 if foundFileCount % 10_000 == 0 { let fileCount = foundFileCount await Task.sleep(1_000_000_000) // <-- Only works with this in...comment out to see failure await MainActor.run { self.fileCount = fileCount } } } let fileCount = foundFileCount await MainActor.run { self.fileCount = fileCount } } } } } The code works if I revert to the old way of achieving this: final class OldFileCounter: ObservableObject { @Published var fileCount = 0 func findImagesInFolder(_ folderURL: URL) { let fileManager = FileManager.default DispatchQueue.global(qos: .userInitiated).async { let options = FileManager.DirectoryEnumerationOptions(arrayLiteral: [.skipsHiddenFiles, .skipsPackageDescendants]) var foundFileCount = 0 if let enumerator = fileManager.enumerator(at: folderURL, includingPropertiesForKeys: [], options: options) { while let _ = enumerator.nextObject() as? URL { foundFileCount += 1 if foundFileCount % 10_000 == 0 { let fileCount = foundFileCount DispatchQueue.main.async { self.fileCount = fileCount } } } let fileCount = foundFileCount DispatchQueue.main.async { self.fileCount = fileCount } } } } } What am I doing wrong? BTW - if you want to try out this code, here is a test harness. Be sure to pick a folder with lots of files in it and its sub-folders. import SwiftUI @main struct TestFileCounterApp: App { var body: some Scene { WindowGroup { ContentView() } } } struct ContentView: View { @State private var showPickerOld = false @StateObject private var fileListerOld = OldFileCounter() @State private var showPickerNew = false @StateObject private var fileListerNew = NewFileCounter() var body: some View { VStack { Button("Select folder to count files using DispatchQueue...") { showPickerOld = true } Text("\(fileListerOld.fileCount)").foregroundColor(.green) .fileImporter(isPresented: $showPickerOld, allowedContentTypes: [.folder], onCompletion: processOldSelectedURL ) Divider() Button("Select folder to count files using Swift 5.5 concurrency...") { showPickerNew = true } Text("\(fileListerNew.fileCount)").foregroundColor(.green) .fileImporter(isPresented: $showPickerNew, allowedContentTypes: [.folder], onCompletion: processNewSelectedURL ) } .frame(width: 400, height: 130) } private func processOldSelectedURL(_ result: Result<URL, Error>) { switch result { case .success(let url): fileListerOld.findImagesInFolder(url) case .failure: return } } private func processNewSelectedURL(_ result: Result<URL, Error>) { switch result { case .success(let url): fileListerNew.findImagesInFolder(url) case .failure: return } } }```
7
0
2.6k
Aug ’21
Swift value type semantics and impact of bindings
I was recently looking at Apple's sample code for a ReferenceFileDocument based SwiftUI document app: Building a Document-Based App with SwiftUI The two main data types, Checklist and ChecklistItem, are defined as structs. There is also a helper struct BindingCollection that converts a binding to a collection of elements into a collection of bindings to the individual elements. The app relies on bindings to these structs. However, in some ways, isn't a binding to a struct circumnavigating value-type semantics? Any piece of code that is passed the Binding can alter the value, rather than a copy of the value? How does SwiftUI know that the struct has been updated without a publisher? Last question: if I'd been writing this app myself, I would have made Checklist a class, conforming to ObservableObject publishing its items property. This would have avoided the helper function, and use of bindings. What are the benefits of the struct / binding approach in the example code?
0
0
626
Aug ’21
Running FileManager.default.enumerator on different tasks
I'm writing a function where a directory and its descendants are scanned to produce a list of files. The function is part of a class, FileScanner. Here's a simplified version of the function: func scanFolder(_ folderURL: URL) async { // Set up configuration for directory search, varies based on need to search sub directories let resourceKeysArray: [URLResourceKey] = [.nameKey, .isDirectoryKey, .fileResourceTypeKey, .creationDateKey, .contentModificationDateKey, .contentTypeKey] let resourceKeysSet = Set&lt;URLResourceKey&gt;(resourceKeysArray) let options = FileManager.DirectoryEnumerationOptions(arrayLiteral: [.skipsHiddenFiles, .skipsPackageDescendants]) if let enumerator = FileManager.default.enumerator(at: folderURL, includingPropertiesForKeys: resourceKeysArray, options: options) { await folderScanStatus.markRunning() while await !folderScanStatus.cancelled, let fileURL = enumerator.nextObject() as? URL { print("\(id) found \(fileURL.path)") // Logging for debug purposes foundFiles.append(fileURL) } } } The code is async, as it has to call some async functions (I've left a couple in for illustration.) The user can have multiple scans in process simultaneously. For each one a new FileScanner is created, and de-inited once the scan has completed - the results are copied across elsewhere. scanFolder is called from within a Task: Task { await scanFolder(someURL) } I am finding that if two of these processes run at once, they can actually interfere with each other - i.e. one can scan the directory of the other. I added some logging to highlight the issue. The log line shows the ID of the scanner, and the file it has found. In this run there were two scanners: EDF43558-608E-47A4-81E5-97B9707B1D0F, scanning /Volumes/Back-up A/ 982EC712-D79E-4785-A1BA-3B53F85967F0, scanning /Users/TEST/ And here's some extracts from the log showing them working as expected: 982EC712-D79E-4785-A1BA-3B53F85967F0 found /Users/TEST/Files/R_24_04-04.txt EDF43558-608E-47A4-81E5-97B9707B1D0F found /Volumes/Back-up A/180704f01.txt And here's an example showing Scanner 982EC712-D79E-4785-A1BA-3B53F85967F0 finding a file that the other one also picked up: 982EC712-D79E-4785-A1BA-3B53F85967F0 found /Volumes/Back-up A/19839f92.txt : EDF43558-608E-47A4-81E5-97B9707B1D0F found /Volumes/Back-up A/19839f92.txt Any ideas why this is happening? I was under the impression FileManager was thread safe?
3
0
1.6k
Nov ’21
SwiftUI app outputting CVDisplayLink related messages to console?
I'm getting a lot of output on the console as I run a MacOS based SwiftUI app I'm developing, for example: 2021-12-08 12:40:14.439565+0000 SpDriveApp[6801:159299] [] [0x7fe6e7830820] CVCGDisplayLink::setCurrentDisplay: 1892262333 2021-12-08 12:40:14.439785+0000 SpDriveApp[6801:159299] [] [0x7fe6e7830800] CVDisplayLinkCreateWithCGDisplays count: 1 [displayID[0]: 0x70c9a1bd] [CVCGDisplayLink: 0x7fe6e7830820] 2021-12-08 12:40:14.439827+0000 SpDriveApp[6801:159299] [] [0x7fe6e7830800] CVDisplayLinkStart 2021-12-08 12:40:14.439853+0000 SpDriveApp[6801:159299] [] [0x7fe6e7830820] CVDisplayLink::start 2021-12-08 12:40:14.439993+0000 SpDriveApp[6801:182706] [] [0x60000f698460] CVXTime::reset Has anyone else seen this? Have I accidentally switched on a diagnostic tool, or is there something else I am doing wrong?
11
0
3.1k
Dec ’21