2

I'm developing a SwiftUI document-based app that contains some easily serializable data plus multiple images. I'd like to save the document as a package (i.e, a folder) with one file containing the easily serialized data and a subfolder containing the images as separate files. My package directory should look something like this:

 <UserChosenName.pspkg>/.    // directory package containing my document data and images
       PhraseSet.dat   // regular file with serialized data from snapshot
       Images/              // subdirectory for images (populated directly from my app as needed)
             Image0.png
             Image1.png
             ....

I've created a FileWrapper subclass that sets up the directory structure and adds the serialized snapshot appropriately but when I run the app in an iOS simulator and click on "+" to create a new document the app runs through the PkgFileWrapper init() and write() without error but returns to the browser window without apparently creating anything. I have declared that the Exported and Imported Type Identifiers conform to "com.apple.package". Can anyone suggest a way to get this working?

The PkgFileWrapper class looks like this:

class PkgFileWrapper: FileWrapper {

var snapshot: Data

init(withSnapshot: Data) {
    self.snapshot = withSnapshot
    let sWrapper = FileWrapper(regularFileWithContents: snapshot)
    let dWrapper = FileWrapper(directoryWithFileWrappers: [:])
    super.init(directoryWithFileWrappers: ["PhraseSet.dat" : sWrapper,
                                           "Images" : dWrapper ])
    
    // NOTE: Writing of images is done outside
    // of the ReferenceFileDocument functionality.
}
    
override func write(to: URL,
                    options: FileWrapper.WritingOptions,
                    originalContentsURL: URL?) throws {
    try super.write(to: to, options: options,
                    originalContentsURL: originalContentsURL)
}

required init?(coder inCoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

}

tdl
  • 297
  • 3
  • 11
  • I actually need the same, but I'm having Sandbox issues. I need it to create a folder and put the Document in that folder. In my main folder it would then create subfolders for images, etc... But I don't know how to set it up and I think I might have Sandbox issues then. – John Nov 21 '21 at 17:51
  • @John, please take a look at my edited answer below. If you are using a FileDocument or ReferenceFileDocument class then it should give you a base URL that shouldn't have Sandbox issues. – tdl Nov 22 '21 at 20:40

1 Answers1

1

The solution is to not override PkgFileWrapper.write(...). If the directory structure is set up correctly in the init(...) then the files and directories will be created automatically. The overridden write(...) function above has now been corrected.

If you want to write an image to the Images subdirectory, you could do something like the following:

func addImage(image: UIImage, name: String) {
    let imageData = image.pngData()!
    imageDirWrapper.addRegularFile(withContents: imageData,
                               preferredFilename: name)

}

The value of imageDirWrapper is the directory wrapper corresponding to the directory that holds your images, as created in PkgFileWrapper.init() above. A key concept you need to keep in mind here is that the "write" function will get called automatically at the appropriate time - you don't explicitly write out your image data. The ReferenceFileDocument class will arrange for that and will also arrange for your app to be passed the appropriate URL for setting up your file wrappers.

The imageDirWrapper variable is set in the required init(...) for the ReferenceFileDocument protocol:

required init(configuration: ReadConfiguration) throws {
    phraseSet = PhraseSet()
    
    
    if configuration.file.isDirectory {
        if let subdir = configuration.file.fileWrappers {
            
            // first load in the phraseSet
            for (name, wrapper) in subdir {
                if name == PkgFileWrapper.phraseSetFileName {
                    if let data = wrapper.regularFileContents {
                        phraseSet = try PhraseSet(json: data)
                    }
                }
            }
            
            // next load in the images and put them into the phrases.
            for (name, wrapper) in subdir {
                if name == PkgFileWrapper.imageDirectoryName {
                    if let imageDir = wrapper.fileWrappers {
                        imageDirWrapper = wrapper
                        for (iName, iWrapper) in imageDir {
                            print("image file: \(iName)")
                            if let d = iWrapper.regularFileContents {
                                for p in phraseSet.phrases {
                                    if p.imageName == iName {
                                        
                                        // TBD: downsample

                                        var uiD = ImageData(data: d)
                                        if doDownSample {
                                            uiD.uiimageData = downsample(data: d,
                                                                         to: imageSize)
                                        } else {
                                            _ = uiD.getUIImage()
                                        }
                                        images[iName] = uiD
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    } else {
        throw CocoaError(.fileReadCorruptFile)
    }

You can see here how imageDirWrapper is set by looking through the passed-in directory's subdirectories for the image directory name. Also some bonus code: it first looks through the passed-in directory for the data file and loads it in; then it looks for the image directory and processes it.

tdl
  • 297
  • 3
  • 11
  • How do you write images to that folder? – John Nov 21 '21 at 20:36
  • @John, please take a look at my edited answer above. – tdl Nov 22 '21 at 20:38
  • Also, Paul Hegarty at Stanford has an excellent discussion of the Document Architecture in Lecture 14 for 2021 at https://cs193p.sites.stanford.edu, though he does not discuss this exact issue. – tdl Nov 22 '21 at 20:45
  • Where is your imageDirWrapper variable located? I have my FileWrappers setup in the func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper, but how can I add files to these folder? – John Nov 23 '21 at 21:48
  • @John, take a look at my recent edit showing the setting of imageDirWrapper. And please upvote my answer if it's useful! – tdl Nov 24 '21 at 16:06