Summary:
Mail appears to flatten document packages when sending them.
Background:
I'm using subclassed UIDocument and FileWrappers to provide a document package for storing metadata, data and a thumbnail preview for my documents. I've made the necessary UTI incantations to get the document packages working locally.
I've added sharing support via UIActivityViewController. I'm sending the document using its fileURL. In testing with round-tripping my document package everything seems to be working well: export and import work great... almost.
Problem:
With Mail attachments, the document package arrives in a semi-flattened state (import no longer sees it as the document package). On examination of the document (on my Mac in TextWrangler) there is still directory structure and I can see the metadata, data, thumbnail but FileManager doesn't see it.
I've tried sharing the Mail attachment back to my app (via Copy to App) before the email is sent and that works fine.
Question:
Is Mail doing something unusual when moving the document package across a mail server?
I'm hoping to eventually support Open in Place and Files so I don't really want to get into wrapping shared documents in a Data blob etc. Does anyone know what's happening here and how to handle it while keeping my document a package?
Update:
I've confirmed that Apple Mail is zipping the package but not changing the extension. So the file received is a zip of the original. How can I make sure Apple Mail sees this as a package and doesn't zip? And, if I can't, how can I recognize the file being imported is a zip without the zip extension?
Answer:
Mail is indeed zipping the document package.
According to my research, we don't need to alter the document-package approach (i.e. not have a flattened special export type). On import, we test if it's a directory. If it's not a directory then we try unzipping it from its source url instead of just copying it. It should be noted that unzipping needs to be handled through a third-party library.
I wish there was a built-in API for handling this but this seems to be the way to deal with it.
(Inelegant) example code:
// Check if directory
if let _ = try? FileManager.default.contentsOfDirectory(atPath: url.path) {
// It is - try copying to the documents url
do {
try FileManager.default.copyItem(at: url, to: destination)
} catch {
self.delegate?.documentFileManagerDidFail(error: NSLocalizedString("Failed to copy file: \(error)", comment: "Document failure message"))
return
}
} else {
// It's not - try unzipping to the documents url
do {
try FileManager.default.unzipItem(at: url, to: destination)
} catch {
self.delegate?.documentFileManagerDidFail(error: NSLocalizedString("Failed to unzip file: \(error)", comment: "Document failure message"))
return
}
}
Related
I would like to create a function that gets the data in my firebase cloud storage. For example, I have 3 separate folders as follows: Movies, Songs and previews. I would like to create a function that will be able to download the url file of the specified folder to get the data of it so I can display it on the selection screen. I have the folders pretty organized. They're in this order, Movies/MovieName/{Movie.jpg (image for the movie), Movie.mp4(video)}. I need my function to open up "Movies" and run through the MovieName file and post each of the contents inside those files. So kinda like a streaming service, I do NOT want to download the url file permanently on the localfile. So kinda think of netflix where they have the image of the movie, with a little description, reviews, etc and it'll stream. You can never download the movie permanently.
"What have I tried?":
I've tried using the firebase link here to guide me but it seems like I'm not understanding it. I've also tried the "list all" list all link but I don't believe it shows the data of each file. Nor was it working for me. Finally I've tried using the URLSession.streamtask()
but I've never used that before and so I'll try to successfully use it now by researching more.
"Some code":
func getAlbums() {
storageref.downloadURL { (url, error) in
if let error = error {
print(error.localizedDescription)
}
else {
//get download url
}
}
According to the firebase link attached this is what I need to do but I don't know how to get the download URL after the else statement.
I am looking for an alternative export menu other then UIActivityViewController for a Mac Catalyst app. While this works, the user can not choose where they want to save the file (the file is a JSON file of all the items in a list) and I would like the user to be able to choose the directory they want to save the JSON to. I have tried the following code but it gives the error "Error Domain=NSCocoaErrorDomain Code=260 'The file 'name.json' couldn’t be opened because there is no such file'" when you try to save a file.
The Code:
let fileManager = FileManager.default
do {
let fileURL2 = fileManager.temporaryDirectory.appendingPathComponent("\(detailedList.lname!).json")
// Write the data out into the file
try jsonData.write(to: fileURL2)
// Present the save controller. We've set it to `exportToService` in order
// to export the data -- OLD COMMENT
let controller = UIDocumentPickerViewController(url: fileURL2, in: UIDocumentPickerMode.exportToService)
present(controller, animated: true) {
// Once we're done, delete the temporary file
try? fileManager.removeItem(at: fileURL2)
}
} catch {
print("error creating file")
}
I have tried Googling other ways or ways to get this to work but I cannot find anything that will work on Mac Catalyst. I know you can do this because I have seen other apps and examples do it but nothing works for me. So what would be a possible alternative way of doing this or a solution to this code?
The problem is that you are removing the file you wish to save before the user has a chance to choose where they want to save it.
The completion handler where you call try? fileManager.removeItem(at: fileURL2) is called as soon as the document picker is displayed.
The proper solution is to delete the file in the UIDocumentPickerDelegate methods, not when the picker is presented.
I have defined a UTI for a custom document format. I can export files from my app and append them to text messages, email, etc. I can import the files into my app by tapping on the document icon in iMessage. By tapping on the document icon, I have the option to copy to my app. That triggers a call in my AppDelegate to handle the incoming file.
What's bugging me is that the url for the incoming file is:
file:///private/var/mobile/Containers/Data/Application/21377C94-1C3C-4766-A62A-0116B369140C/Documents/Inbox/...
Whereas, when saving documents to the .documents directory, I use this URL:
file:///var/mobile/Containers/Data/Application/21377C94-1C3C-4766-A62A-0116B369140C/Documents/...
The difference being the /private/ and /Inbox/ path components.
Question: how can I purge the /private/.../Inbox/ path of the files that were copied to my app from iMessage? I noticed this when testing my app and when I tapped on the same document icon in iMessage it started generating file copies with the same name but adding -1, then -2, then -3 to the file name of the document from iMessage. It appears that copies are building up in that /private/.../Inbox/ path.
Is that something that gets flushed on its own or can I access that directory to remove those files? It's also annoying because based upon the filename, it appears to be a different file thus allowing multiple copies of the same file to be imported with a slightly different file name.
Ok, this took a fair amount of digging, but I'll post my solution that seems to work thus far in case anyone runs across the same problem.
let fileManager = FileManager.default
// get the URL for the "Inbox"
let tmpDirURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent("Inbox")
// get all the files in the "Inbox" directory
let anythingThere = try? fileManager.contentsOfDirectory(at: tmpDirURL, includingPropertiesForKeys: nil)
if anythingThere?.count ?? 0 > 0 {
for eachURL in anythingThere! {
// for each url pointing to a file in that directory, get its path extension
let pathExtension = eachURL.pathExtension
// test to see if it's a UTI that you're interested in deleting
// in my case, the three "OCC" strings are the relevant UTI extensions
if pathExtension == "OCCrcf" || pathExtension == "OCCrdi" || pathExtension == "OCCsrf" {
// attempt to delete the temporary file that was copied to the
// "Inbox" directory from importing via email, iMessage, etc.
try fileManager.removeItem(at: eachURL)
}
}
}
If anyone has a more elegant solution, please respond as well. Thanks.
I'm trying to get my iPhone app to load text from a file into a string array, with 1 line from the file per array element.
I've created an input file as a text file using sublime text. I dragged the file (which is located inside of a folder inside of my project directory) into xCode into a folder in the same location in the project heirarchy.
I also tried adding it as a bundle (by copying the folder and renaming it with the .bundle extension), to no avail. Currently, my app has the file in 2 places (Obviously I plan to delete the unneeded version, but I'm not sure how this will work so I've left it for now).
I've written a function that I want to read my file, and assemble its contents into an array:
func readFromFile(filename: String) -> [String]? {
guard let theFile = Bundle.main.path( forResource: fileName, ofType: "txt") else {
return nil // ALWAYS returns nil here: Seems 'filename' can't be found?????
}
do { // Extract the file contents, and return them as a split string array
let fileContents = try String(contentsOfFile: theFile)
return fileContents.components(separatedBy: "\n")
} catch _ as NSError {
return nil
}
}
As it stands, the function always returns nil at the location commented in the code.
I've been working on this for ~6hrs (and tried every suggestion I could find on StackOverflow, google etc) and I'm just getting more and more confused by the differences between the various versions of Swift and intricacies of iOS development. I can't seem to find a consistent answer anywhere. I've checked the apple documentation but it's too high level with no example code for me to understand at my swift beginner level.
I also tried naming the file with a ".txt" extension but that didn't help either.
The file must certainly be named alert01.txt if you are going to refer to it as forResource: "alert01", ofType: "txt".
Loading from a bundle will not work. The file needs to be part of your project as shown in the first entry.
However, your code is not going to work because you have created a folder reference. That means the folder PanicAlertFiles is being copied with all its contents into your bundle. Your code will need to dive into that folder in order to retrieve your file. Use path(forResource:ofType:inDirectory:) to do that, or (if you don't want to have to code the file name explicitly) get the folder and then use the FileManager to examine its contents.
I am trying to use a bundled realm file without success. I know that my realm file was copied successfully to my application’s Directory but I ca not read it.
fatal error: 'try!' expression unexpectedly raised an error: "Unable
to open a realm at path
'/Users/…/Library/Developer/CoreSimulator/Devices/…/data/Containers/Data/Application/…/Documents/default-v1.realm'.
Please use a path where your app has read-write permissions."
func fullPathToFileInAppDocumentsDir(fileName: String) -> String {
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory,NSSearchPathDomainMask.UserDomainMask,true)
let documentsDirectory = paths[0] as NSString
let fullPathToTheFile = documentsDirectory.stringByAppendingPathComponent(fileName)
return fullPathToTheFile
}
In didFinishLaunchingWithOptions:
let fileInDocuments = fullPathToFileInAppDocumentsDir("default-v1.realm")
if !NSFileManager.defaultManager().fileExistsAtPath(fileInDocuments) {
let bundle = NSBundle.mainBundle()
let fileInBundle = bundle.pathForResource("default-v1", ofType: "realm")
let fileManager = NSFileManager.defaultManager()
do {
try fileManager.copyItemAtPath(fileInBundle!, toPath: fileInDocuments)
} catch { print(error) }
}
And setting the configuration used for the default Realm:
var config = Realm.Configuration()
config.path = fileInDocuments
Realm.Configuration.defaultConfiguration = config
let realm = try! Realm(configuration: config) // It fails here!!! :-)
As the documentation suggests, I have tried as well to open it directly from the bundle path by setting readOnly to true on the Realm.Configuration object. I am not sure if this is something related to Realm or if I am overlooking something with the file system…. I have also tried to store the file in the Library folder.
Realm 0.97.0
Xcode Version 7.1.1
I tried to open the realm file using Realm's browser app and the file does not open anymore. It has now new permissions: Write only (Dropbox). So, I decided to change the file permission back to read/write using file manager’s setAttributes method. Here is how I did it:
// rw rw r : Attention for octal-literal in Swift "0o".
let permission = NSNumber(short: 0o664)
do {
try fileManager.setAttributes([NSFilePosixPermissions:permission], ofItemAtPath: fileInDocuments)
} catch { print(error) }
The realm file can now be open at this path.
That exception gets thrown whenever a low level I/O operation is denied permission to the file you've specified (You can check it out on Realm's GitHub account).
Even though everything seems correct in your sample code there, something must be set incorrectly with the file location (Whether it be the file path to your bundle's Realm file, or the destination path) to be causing that error.
A couple of things I can recommend trying out.
Through breakpoints/logging, manually double-check that both fileInDocuments and fileInBundle are being correctly created and are pointing at the locations you were expecting.
When running the app in the Simulator, use a tool like SimPholders to track down the Documents directory of your app on your computer, and visually ensure that the Realm file is being properly copied to where you're expecting.
If the Realm file is in the right place, you can also Realm's Browser app to try opening the Realm file to ensure that the file was copied correctly and still opens properly.
Try testing the code on a proper iOS device to see if the same error is occurring on the native system.
If all else fails, try doing some Realm operations with the default Realm (Which will simply deposit a default.realm file in your Documents directory), just to completely discount there isn't something wrong with your file system
Let me know how you go with that, and if you still can't work out the problem, we can keep looking. :)
This will occur if you have your realm file open in Realm Studio at same time you relaunch your app. Basically in this case Realm can't get write permissions if Studio already has them.
To add to the solution based on what I've discovered, make note of what error Realm reports when it throws the exception, as well as the type of Error that is passed.
As of writing, Realm documents their errors here:
https://realm.io/docs/objc/latest/api/Enums/RLMError.html
What this means is that you can find out if your Realm file has permissions problems and react to those based on Realm passing you a RLMErrorFilePermissionDenied. Or if the file doesn't exist with RLMErrorFileNotFound.
The tricky thing I'm finding is when you get a more generic RLMErrorFileAccess, but that's for another question on Stack Overflow...
I had the same issue and tried too many ways to fix it. The easiest way to fix this problem is manually creation of the folder XCode cannot reach '/Users/…/Library/Developer/CoreSimulator/Devices/…/data/Containers/Data/Application/…/Documents/...' as explained at https://docs.realm.io/sync/using-synced-realms/errors#unable-to-open-realm-at-path-less-than-path-greater-than-make_dir-failed-no-such-file-or-directory
Once you created this folder and run the project, XCode creates the Realm files inside this folder automatically.