SwiftUI + DocumentGroup in iOS/iPadOS: How to rename the currently open document

How to solve SwiftUI + DocumentGroup in iOS/iPadOS: How to rename the currently open document?

My question is about SwiftUI's DocumentGroup: Using a simple, template-based project in Xcode (using the new multi-platform, document-based application template), I can create new documents, edit them, etc. Also, "outside" the application, I can manipulate the document file like this  - move it, copy it, rename it, etc.

By default the name "Untitled" ; at the main application entry point, I can access the file 's URL:

 
 
  1. var body: some Scene {
  2. DocumentGroup(newDocument: ShowPLAYrDocument()) { file in
  3. // For example,this gives back the actual doc file URL:
  4. let theURL = file.fileURL
  5. ContentView(document: file.$document)
  6. }
  7. }

First question: How do I edit/change the actual filename after the document is "opened" , i.e. when the code is running in  ContentView scope?  The lack of SwiftUI documentation makes finding answers to such questions very difficult - I think I've scoured the entire internet raging on, but no one seems to be having this type of problem, and if they have, their posted questions have no answers - I've posted a couple of questions myself on other questions that don't even have any comments, let alone answers .

I have another question, which I think is somewhat related: For example, I've seen in the Files app that certain file types, when selected, can display additional extended information under the Info pane for that file (eg: video file with dimensions in pixels, duration and codec information ); my app's document contains several values ​​(in saved data) and I want the user to be able to "browse" in the document picker without opening the file itself, in a manner similar to that described in the Files application.

My second question is: is this possible, and if so, where can I at least start looking for an answer? My guess is that this isn't "possible" for SwiftUI now, it has to be integrated with "regular" Swift?

Thanks in advance for your guidance.

Solution

Ok, so here's the thing: I "sort of" managed to achieve what I'm after, although it doesn't look like (to me) the most "correct" way to do it, and there are still issues with the process - although for now I'm going to It's blamed on an (apparently known) flawed  DocumentGroup implementation that caused other problems as well (see this question  for more details on that).

The way I "sort of" managed to change the filename looks like this:

 
 
  1. @main
  2. struct TestApp: App {
  3. @State var previousFileURL: String = ""
  4. var body: some Scene {
  5. DocumentGroup(newDocument: TestDocument()) { file in
  6. ContentView(document: file.$document)
  7. .onAppear() {
  8. previousFileURL = file.fileURL!.path
  9. }
  10. .onDisappear() {
  11. let newFileName = "TheNewFileName.testDocument"
  12. let oldFileName = URL(fileURLWithPath: previousFileURL).lastPathComponent
  13. var newURL = URL(fileURLWithPath: previousFileURL).deletingLastPathComponent()
  14. newURL.appendPathComponent(newFileName)
  15. do {
  16. try FileManager.default.moveItem(atPath: oldURL.path,toPath: newURL.path)
  17. } catch {
  18. print("Error renaming file! Threw: \(error.localizedDescription)")
  19. }
  20. }
  21. }
  22. }
  23. }

What it does is: after the view is initialized (in  previousFileURL ), it  .onAppear "stores" it in a state variable by assigning the document's initial URL in the decorator (I did this because I don't know how to get access to the closure in  file the passed  DocumentGroup by reference). Then, by using  .onDisappear modifiers, which I use  FileManager to  moveItem assign the new name - just "move" the file from the previous URL to the newly generated URL (which should actually rename the file); the sample code provided uses hardcoded strings,  newFileNamebut In my actual code (it's actually too long to post here) I extract this new filename from a value stored in the actual document, which is again a string that the app user can change when the document is opened EDIT (makes sense?).

question

This is currently having a very annoying problem: in one set of circumstances (i.e. when the app is just launched, and a new document is created using the "plus" button), the code behaves as I would expect to - it opens new document, I can (using the "content view") edit (and store) the string that will become the filename, and when I "close it" (using the back button on the navigation view), it will update the filename accordingly , which I can confirm by actually viewing the file in the documentation browser.

BUT... if I reopen the same file, or use another file, or just go through the whole process of creating a new file again without closing the app, then obviously somehow that will mess up  DocumentGroupthe  FileManager operation  moveItem actually To the extent that the file is copied (with the new name) but the "old" file is not deleted or actually renamed, so you end up with two files: one with the new name and one with the "old"/previous name.

This happens even if I check for the existence of the old file: when it hits these conditions, it FileManager.default.fileExists actually finds the previous/old file, but when it "moves" to the new name it copies it instead of renaming it. Weird, but I'm assuming it's because of the (obvious) bug I mentioned in the link above.

Hopefully this leads to a better answer for someone with more experience and understanding, who will (hopefully) share it here.

,

The problem encountered with the "solution" above  .fileImporter is related to the (confirmed) bug with modifiers - so this "works", nonetheless.

,

Have you tested the "hacky" solution above on your device? It works fine on simulator, but due to  new access permission rules in iOS 13 , the code throws “XXXXXX” couldn’t be moved because you don’t have permission to access “YYYYYY”.

 I've dug a little deeper and tried overriding the standard and  function definitions init() of the standard code  generated by XCode   , setting the required filename as  a property  of   and  :FileWrapperDocument.swiftpreferredFilenamefilenameFileWrapper

 
 
  1. struct SomeDocument: FileDocument,Decodable,Encodable {
  2. static var readableContentTypes: [UTType] { [.SomeDocument] }
  3. var someData: SomeCodableDataType
  4. init() {
  5. self.someData = SomeCodableDataType()
  6. print("Creating.\n")
  7. }
  8. init(configuration: ReadConfiguration) throws {
  9. guard let data = configuration.file.regularFileContents else {
  10. throw CocoaError(.fileReadCorruptFile)
  11. }
  12. let savedPreferredName = configuration.file.preferredFilename
  13. let savedName = configuration.file.preferredFilename
  14. let fileRep = try JSONDecoder().decode(Self.self,from: data)
  15. self.someData = fileRep.someData
  16. print("Loading.\n Filename: \(savedPreferredName ?? "none") or \(savedName ?? "none")\n")
  17. }
  18. func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
  19. do {
  20. let fileRep = try JSONEncoder().encode(self)
  21. let fileWrapper = FileWrapper.init(regularFileWithContents: fileRep)
  22. fileWrapper.preferredFilename = fileName()
  23. fileWrapper.filename = fileName()
  24. print("Writing.\n Filename \(fileWrapper.preferredFilename ?? "none") or \(fileWrapper.filename ?? "none").\n")
  25. return fileWrapper
  26. } catch {
  27. throw CocoaError(.fileReadCorruptFile)
  28. }
  29. }
  30. func fileName() -> String {
  31. let timeFormatter = DateFormatter()
  32. timeFormatter.dateFormat = "yyMMdd'-'HH:mm"
  33. let timeStamp = timeFormatter.string(from: Date())
  34. let extention = ".ext"
  35. let newFileName = timeStamp + "-\(someData.someUniqueValue())" + extention
  36. return newFileName
  37. }
  38. }

This is what the console prints out. I'm in square brackets []:

Added user action in

 
 
  1. [CREATE DOC BY TAPPING +]
  2. Creating.
  3. [AUTOMATIC WRITE]
  4. Writing.
  5. Filename 210628-16:49-SomeUniqueValue.ext or 210628-16:49-SomeUniqueValue.ext.
  6. [AUTOMATIC LOAD]
  7. Loading.
  8. Filename: none or none
  9. FileURL: /Users/bora/Library/Developer/CoreSimulator/Devices/F126086A-A752-4A71-B589-1B37DFC02746/data/Containers/Data/Application/D81C9D76-7986-4C0D-BA2C-1FDF69703875/Documents/Untitled 2.ext
  10. isEditable: true
  11. [CLOSING DOC]
  12. Writing.
  13. Filename 210628-16:49-SomeUniqueValue.ext or 210628-16:49-SomeUniqueValue.ext.
  14. [REOPENING DOC]
  15. Loading.
  16. Filename: none or none
  17. FileURL: /Users/bora/Library/Developer/CoreSimulator/Devices/F126086A-A752-4A71-B589-1B37DFC02746/data/Containers/Data/Application/D81C9D76-7986-4C0D-BA2C-1FDF69703875/Documents/Untitled 2.ext
  18. isEditable: true

So after the initial document creation, the first write (use  func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper), the filename is correctly assigned to  FileWrapper. However, when the view code loads the document, it's clear that there is no  FileWrapper filename name attribute used. FileWrapper The same thing repeats when the document is closed (with  write and given a name) and opened again.

This looks like a bug. I don't understand why DocumetGroup doesn't use FileWrapper the filename attribute, but definitely uses the same for FileWrapper the provided data content.

I haven't tried this on the new SwiftUI (iOS14). I'll do it and report back.
 

UPDATE: Now tested on iOS 14, doesn't work there either. I think it's time to start the radar.

Guess you like

Origin blog.csdn.net/weixin_42610770/article/details/129575352