如何从基于 SwiftUI 文档的应用程序中将多个文件保存在包文件夹中?

Posted

技术标签:

【中文标题】如何从基于 SwiftUI 文档的应用程序中将多个文件保存在包文件夹中?【英文标题】:How do I save multiple files in a package folder from a SwiftUI Document Based App? 【发布时间】:2021-09-24 19:40:40 【问题描述】:

我正在开发一个基于 SwiftUI 文档的应用程序,其中包含一些易于序列化的数据和多个图像。我想将文档保存为一个包(即文件夹),其中一个文件包含易于序列化的数据,另一个子文件夹包含图像作为单独的文件。我的包目录应该是这样的:

 <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
             ....

我创建了一个 FileWrapper 子类,它设置目录结构并适当地添加序列化快照,但是当我在 ios 模拟器中运行应用程序并单击“+”以创建一个新文档时,应用程序通过 PkgFileWrapper init 运行() 和 write() 没有错误,但返回浏览器窗口而没有明显地创建任何东西。我已声明导出和导入类型标识符符合“com.apple.package”。任何人都可以建议一种方法来使其正常工作吗?

PkgFileWrapper 类如下所示:

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")

【问题讨论】:

我实际上需要相同的,但我遇到了沙盒问题。我需要它来创建一个文件夹并将文档放在该文件夹中。然后在我的主文件夹中,它会为图像等创建子文件夹......但我不知道如何设置它,我想我可能会遇到沙盒问题。 @John,请看下面我编辑的答案。如果您使用的是 FileDocument 或 ReferenceFileDocument 类,那么它应该为您提供一个不应该有沙盒问题的基本 URL。 【参考方案1】:

解决方案是不要覆盖 PkgFileWrapper.write(...)。如果在 init(...) 中正确设置了目录结构,则将自动创建文件和目录。上面重写的 write(...) 函数现已更正。

如果您想将图像写入图像子目录,您可以执行以下操作:

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

imageDirWrapper 的值是与保存图像的目录相对应的目录包装器,如上面的 PkgFileWrapper.init() 中创建的。您需要记住的一个关键概念是“写入”函数将在适当的时间自动调用 - 您无需明确写出图像数据。 ReferenceFileDocument 类会为此安排,还会安排您的应用通过适当的 URL 来设置文件包装器。

imageDirWrapper 变量在 ReferenceFileDocument 协议所需的 init(...) 中设置:

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)
    

您可以在此处通过查看传入目录的子目录中的图像目录名称来查看 imageDirWrapper 是如何设置的。还有一些额外的代码:它首先在传入的目录中查找数据文件并将其加载;然后它会查找图像目录并对其进行处理。

【讨论】:

如何将图像写入该文件夹? @John,请看我上面编辑的答案。 此外,斯坦福大学的 Paul Hegarty 在 cs193p.sites.stanford.edu 的 2021 年第 14 讲中对文档架构进行了精彩的讨论,尽管他没有讨论这个确切的问题。 你的 imageDirWrapper 变量在哪里?我在 func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper 中设置了我的 FileWrappers,但是如何将文件添加到这些文件夹中? @John,看看我最近的编辑,显示了 imageDirWrapper 的设置。如果有用,请点赞我的回答!

以上是关于如何从基于 SwiftUI 文档的应用程序中将多个文件保存在包文件夹中?的主要内容,如果未能解决你的问题,请参考以下文章

基于文档的 MacOs 应用程序的 SwiftUI 窗口大小

如何在 SwiftUI ForEach 内容中将多个按钮操作分开?

如何在 SwiftUI 中将数据从视图传递到另一个视图?

iOS/iPadOS 中的 SwiftUI + DocumentGroup:如何重命名当前打开的文档

如何在 Swift 5.5 中将 async/await 与 SwiftUI 一起使用?

在 SwiftUI 中将视图添加到层​​次结构时如何动画过渡