I need a FileWrapper which contains a file and contains a folder.
The file is a single file, and the folder is used to write images to.
The folder also can contain some subfolders. I have a working code, but the issue is that when the Document gets saved, the folder gets re-written which deletes my images and subfolders.
I'm quite sure it has something to do with func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper but I need some help from somebody with more experience with FileWrappers.
This is my code:
struct MyProject: FileDocument {
var myFile: MyFile
static var readableContentTypes: [UTType] { [.myf] }
init(myFile: MyFile = MyFile() {
self.myFile = myFile
}
init(configuration: ReadConfiguration) throws {
let decoder = JSONDecoder()
guard let data = configuration.file.fileWrappers?["MYFProject"]?.regularFileContents else {
throw CocoaError(.fileReadCorruptFile)
}
do {
self.myFile = try decoder.decode(MyFile.self, from: data)
} catch {
throw error
}
}
func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
let encoder = JSONEncoder()
do {
let data = try encoder.encode(myFile)
let mainDirectory = FileWrapper(directoryWithFileWrappers: [:])
let myfWrapper = FileWrapper(regularFileWithContents: data)
let imagesWrapper = FileWrapper(directoryWithFileWrappers: [:])
let imageSubFolder = FileWrapper(directoryWithFileWrappers: [:])
for numberString in myFile.numbers {
imageSubFolder.preferredFilename = numberString
imagesWrapper.addFileWrapper(imageSubFolder)
}
myfWrapper.preferredFilename = "MYFProject"
mainDirectory.addFileWrapper(myfWrapper)
imagesWrapper.preferredFilename = "MYFImages"
mainDirectory.addFileWrapper(imagesWrapper)
return mainDirectory
} catch {
throw error
}
}
}
I use this path to write images to.
func getSubFolderImageFolder(documentPath: URL, subFolder: String) -> URL {
let sfProjectPath = documentPath.appendingPathComponent("MYFImages").appendingPathComponent(subFolder)
if !FileManager.default.fileExists(atPath: sfProjectPath.path) {
do {
try FileManager.default.createDirectory(atPath: sfProjectPath.path, withIntermediateDirectories: false, attributes: nil)
return sfProjectPath
} catch {
fatalError(error.localizedDescription)
}
}
else {
return sfProjectPath
}
}
Thanks in advance!
Your getSubFolderImageFolder function is not going to work well with file wrappers. You must use the FileWrapper methods to create the folders and files in the file wrapper.
To add a subfolder to your images folder, create a directory file wrapper the same way you created the imagesWrapper folder for the images. Add the subfolder as a child of the images folder.
let imageSubFolder = FileWrapper(directoryWithFileWrappers: [:])
imagesWrapper.addFileWrapper(imageSubFolder)
You must create a directory file wrapper for each subfolder. I notice in your updated code, you have only one subfolder file wrapper. With only one subfolder file wrapper, you have no way to store an image file in the correct subfolder.
To add the images, start by converting each image into a Data object. Create a regular file wrapper for each image, passing the image data as the argument to regularFileWithContents. Call addFileWrapper to add the image file to the appropriate folder.
let imageFile = FileWrapper(regularFileWithContents: imageData)
imageFile.preferredFilename = "ImageFilename" // Replace with your filename.
imagesWrapper.addFileWrapper(imageFile)
In your case the image subfolders will call addFileWrapper to add the images. The destination for the image file calls addFileWrapper.
You can find more detailed information about file wrappers in the following article:
Using File Wrappers in a SwiftUI App
Related
)
today I have a problem and I can't find an easy solution.
With:
FileManager().fileExists(atPath:(fileURL.path))
it's simple to find out if a file exist. Actually I have the file name but don't know the extension. How can I use FileManager() to find a file without the extension. Something like .deletingPathExtension() for FileManger().fileExists?
Something like
ls filename.*
You could create a FileManager extension that retrieves the contents of the directory and filters for files as well as the expected filename.
It might look something like this:
extension FileManager {
func urls(of filename: String, in directory: URL) -> [URL]? {
guard let urls = try? contentsOfDirectory(at: directory, includingPropertiesForKeys: nil, options: [])
else { return nil }
return urls.filter { url in
!url.hasDirectoryPath && url.deletingPathExtension().lastPathComponent == filename
}
}
}
Finally, one would call it something like this:
let directory = URL(string: "file:///Users/stephan/tmp")!
if let urls = FileManager.default.urls(of: "test", in: directory) {
for url in urls {
print("do something with url: \(url)")
}
}
I am using File Manager to write files to the users documents directory. Every file is downloaded to a users device via a URL and then I create a local URL by doing the following:
extension URL {
func getLocalUrl() -> URL {
let directoryURL = FileManager.default.getDocumentsDirectory()
let filePath = pathComponents.dropFirst().joined(separator: "-")
return directoryURL.appendingPathComponent(filePath)
}
}
This works perfectly fine. However, when I try to create a local URL by using slashes instead of dashes via the following:
extension URL {
func getLocalUrl() -> URL {
let directoryURL = FileManager.default.getDocumentsDirectory()
let filePath = pathComponents.dropFirst().joined(separator: "/")
return directoryURL.appendingPathComponent(filePath)
}
}
I get the following error when this code runs:
func save(url: URL, fileUrl: URL) {
do {
// fileUrl is a url in the temporary directory from a URLSession.downloadTask
try FileManager.default.moveItem(at: fileUrl, to: url.getLocalUrl())
} catch {
print("download.service.write.error: \(error)")
}
}
CFNetworkDownload_sKaBto.tmp” couldn’t be moved to “user-data” because either the former doesn’t exist, or the folder containing the latter doesn’t exist."
As you can see the error is because I am trying to write to a directory that does not exist. Is there a way to auto create the directory if it does not exist?
I’m trying to get String from txt file inside the zip file using native libcompression library. Actually I use the code from
https://github.com/mw99/DataCompression/blob/master/Sources/DataCompression.swift.
At first, I was doing:
let zip = try? Data(contentsOf: "/.../test.zip")
let tmp: Data? = zip?.unzip()
let txt: String? = String(data: tmp!, encoding: .utf8)
But how do I get the contents of zip file and how do I get data from certain txt file?
ZIP Foundation supports accessing individual entries in ZIP archives.
You have to initialize an archive by passing a file URL to the Archive initializer.
Afterwards you can access a specific entry via subscripting:
let fileManager = FileManager()
let currentWorkingPath = fileManager.currentDirectoryPath
var archiveURL = URL(fileURLWithPath: currentWorkingPath)
archiveURL.appendPathComponent("test.zip")
guard let archive = Archive(url: archiveURL, accessMode: .read) else {
return
}
guard let entry = archive["file.txt"] else {
return
}
var destinationURL = URL(fileURLWithPath: currentWorkingPath)
destinationURL.appendPathComponent("out.txt")
do {
try archive.extract(entry, to: destinationURL)
} catch {
print("Extracting entry from archive failed with error:\(error)")
}
You can also directly access the contents of entry by using the closure based API. This allows you to process the entry without writing it to the file system first:
try archive.extract(entry, consumer: { (data) in
print(data.count)
})
I can check if one file exists with this method:
let fileNameOne = "savedpicture1"
let fileURLOne = documentsDirectoryURL.appendingPathComponent(fileNameOne)
if !FileManager.default.fileExists(atPath: fileURLOne.path) {
removeImage(itemName: "savedpicture1", fileExtension: "jpg")
} else {
print("There was no image to remove")
}
My problem is having to repeat the same lines of code for multiple files. For instance, I would like to check if the files exist in an array of paths, but I would have to repeat the code from above for each file, and it seems too redundant. I'm wondering if there's a way to check multiple files instead of repeating the code for each single path. ".fileExists" only enables me to check one path:
let filePaths = [fileURLOne.path, fileURLTwo.path, fileURLThree.path,
fileURLFour.path]
Write a method for example
func checkFiles(with fileNames: [String] {
for fileName in fileNames {
let fileURL = documentsDirectoryURL.appendingPathComponent(fileName)
if !FileManager.default.fileExists(atPath: fileURL.path) {
removeImage(itemName: fileName, fileExtension: "jpg")
} else {
print("There was no image to remove at", fileURL)
}
}
}
and call it
let fileNames = ["savedpicture1", "savedpicture2", "savedpicture3", "savedpicture4"]
checkFiles(with: fileNames)
I'm building a simple program in Swift, it should copy files with a certain extension to a different folder. If this folder exist the program will just copy them in the folder, if the folder doesn't exist, the program has to make it first.
let newMTSFolder = folderPath.stringByAppendingPathComponent("MTS Files")
if (!fileManager.fileExistsAtPath(newMTSFolder)) {
fileManager.createDirectoryAtPath(newMTSFolder, withIntermediateDirectories: false, attributes: nil, error: nil)
}
while let element = enumerator.nextObject() as? String {
if element.hasSuffix("MTS") { // checks the extension
var fullElementPath = folderPath.stringByAppendingPathComponent(element)
println("copy \(fullElementPath) to \(newMTSFolder)")
var err: NSError?
if NSFileManager.defaultManager().copyItemAtPath(fullElementPath, toPath: newMTSFolder, error: &err) {
println("\(fullElementPath) file added to the folder.")
} else {
println("FAILED to add \(fullElementPath) to the folder.")
}
}
}
Running this code will correctly identify the MTS files but then result in a "FAILED to add...", what am I doing wrong?
From the copyItemAtPath(...) documentation:
dstPath
The path at which to place the copy of srcPath. This path must
include the name of the file or directory in its new location. ...
You have to append the file name to the destination directory for the
copyItemAtPath() call (code updated for Swift 3 and later)
let srcURL = URL(fileURLWithPath: fullElementPath)
let destURL = URL(fileURLWithPath: newMTSFolder).appendingPathComponent(srcURL.lastPathComponent)
do {
try FileManager.default.copyItem(at: srcURL, to: destURL)
} catch {
print("copy failed:", error.localizedDescription)
}