How to handle "open in" retrieved file with swift 2 - swift

I'm trying to build a quite simple app which uploads files to our server using standard http requests.
I'd like the app to be some sort of hub being able to open an email attachement for example.
That worked out, I added the info to the info.plist.
Now that file is sent to my app successfully (eg a pdf file)...
How can I retrieve that file url to display it for example in a webview ? I googled for hours, seems to fo through the appDelegate but I have no idea how that continues...
Just found this code, now I'm stucked (and don't even know if that actually works !)
func application(application: UIApplication, openURL url: NSURL, sourceApplication: String?, annotation: AnyObject?) -> Bool {
let dictionary = NSDictionary(contentsOfURL: url)
return true
}
Any help to point me in the right direction is highly appreciated ! I started using swift 5 days ago, please be gentle ;)

I have not tested this, but it appears that what you want to do is this:
Go to your project settings
Go to the "info" tab
Under "Document Types" add the document types you want to support
To get the added file, you need to look in the Documents/Inbox folder for your app, as shown below:
let filemgr = NSFileManager.defaultManager()
let documentsDirectory = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as NSString
let inboxPath = documentsDirectory.stringByAppendingPathComponent("Inbox")
do {
let dirFiles = try filemgr.contentsOfDirectoryAtPath(inboxPath)
} catch {
//Handle error
}
I am not certain what exactly gets passed to application:openURL:sourceApplication:annotation:, but it might help in checking which file was opened.
Additionally, straight from Apple:
Use this directory to access files that your app was asked to open by outside entities. Specifically, the Mail program places email attachments associated with your app in this directory. Document interaction controllers may also place files in it.
Your app can read and delete files in this directory but cannot create new files or write to existing files. If the user tries to edit a file in this directory, your app must silently move it out of the directory before making any changes.
This answer adapted from this tutorial (part 2 & part 3)

Related

Opening file from Files app when file not downloaded (using Open In Place)

I want my users to be able to tap on a file in the Files app and have it sent to my app. Previously I've had the app setup to successfully be able to open files from Mail when files are shared that way using "Open in ". The filetype I'm using is custom to my app. The document type, Exported UTIs, and Imported UTIs were created in order to enable "Open in " and hasn't been changed.
I'm getting an error if the file hasn't been downloaded inside the Files app already (the small cloud with a down arrow is next to the filename). Even if you long tap on it and select "Share" and the use "Open in ", I get the same error.
If the file was added to Files from that device, it's already downloaded and tapping on it in Files works just fine. How can I force Files to download the file before sending it over to my app? I've searched online and the only reference I can find is here: https://forums.developer.apple.com/thread/47890 which the very last post talks about the same problem, but I can't find a solution anywhere.
In my info.plist file, I have my app setup to "Support opening documents in place" set to YES. I also have set "Application supports iTunes file sharing" set to YES.
I'm checking the value of options[.openInPlace] from the application(_:open:options:) method to detect that the file is coming from the Files app. I had to add the call to url.startAccessingSecurityScopedResource() before it would work at all when the file was already downloaded on the device.
Here's a stripped down version of the code that's doing the processing:
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
let isOpenInPlace = options[.openInPlace] as? Bool
var securityScopedResource = false
if isOpenInPlace {
securityScopedResource = url.startAccessingSecurityScopedResource()
}
do {
let data = try Data(contentsOf: url)
// Use the data to create something inside my app
if securityScopedResource {
url.stopAccessingSecurityScopedResource()
}
} catch {
print("ERROR: \(error.localizedDescription)")
if securityScopedResource {
url.stopAccessingSecurityScopedResource()
}
}
The error I get is:
ERROR: The file <filename.ext> couldn't be opened because there is no such file
Is there any way to get this to work or is this an issue with Files?
(I'm using XCode 10.3, and swift 5, and testing on real devices: iphone 6 with iOS 11.4 and an iPadPro with iOS 12.1)
Edited on Aug 18 to add:
Further research and I've discovered I need to request that the file be downloaded from within my app. I've added the following to my code when I detect open in place = true, and
the file doesn't already exist:
try FileManager.default.startDownloadingUbiquitousItem(at: url)
This method starts the download, but it's async and returns immediately. I'm trying to figure out a reliable way to kick off the download, and wait until it's completed before moving to the next step in my app. Any help would be appreciated!

Swift - Grab all the files from a specific folder/remote URL on a server

I am trying to access a specific folder in a remote URL e.g.http://dev.servertest.com/sessions/id/video
from there, I want to grab all the videos from the folder and download them to the device.
I know how to download the videos from a remote URL to the directory but I can't work out to grab all the videos from the specific video folder.
Also, the server may change depending on the user using their own server but the rest of the structure should be the same "/sessions/id/video"
Below is the code I use to upload from a specific folder "ImportVideos" from my app directory if that helps.
func loadVideos(){
let fm = FileManager.default
let dirPaths = fm.urls(for: .documentDirectory, in: .userDomainMask)
let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0];
do {
let videoList = try fm.contentsOfDirectory(atPath: "\(documentsPath + "/ImportVideo")")
for filename in videoList {
videoSubtitles.append(filename) //this is used to populate my tableview
}
} catch let error {
print("error: \(error.localizedDescription)")
}
}
If someone could point me in the right direction that would be great. Thanks.
The HTTP protocol does not provide any means of getting a list of files in a directory. What you're trying to do is not generally possible without a manifest — a list of files that you want to download.
The easiest way to do that is to run a script on the server, e.g.
#!/bin/sh
FILES_DIR=/path/to/directory/on/server
cd "$FILES_DIR"
ls > allfiles.txt
Then have your app fetch the file http://example.com/path/to/allfiles.txt, then split it by newline, and fetch each file. This approach also provides the advantage of letting you later replace that text file with a script handler that serves different file lists to different clients, if desired, e.g. for supporting different versions of your app.
Alternatively, if your server supports WebDAV, then it is possible to configure the directory with WebDAV enabled and use a WebDAV library to find out what files are in the directory. However, this is probably not a good idea, because WebDAV is relatively complex and easy to misconfigure.
Finally, some web servers provide a way to enable "directory listings" — a web page that has links to all of the files in the directory. (Many people will tell you that this is a bad idea from a security perspective, though that is debatable.) It is possible, though inherently fragile, to parse such a page and extract the links. This approach is strongly discouraged, however, because it could break completely when you update to a new version of the web server software.

Get the content of iCloud Drive

In my viewDidLoad, I need to verify if the files that I saved to the iCloud Drive are still available and have not been deleted. As I have read, I can not use standard FileManager calls:
FileManager.default.fileExists(atPath: filePath)
What would be the alternative. I do use NSMetadataQuery, but I wanted to know if there is an easy way to query the content of the App's UbiquitousDocument prior to the notifications kicking in.
Also, I am using the ios11 facility of sharing files between different users, and again I need to be able to verify if those files are still available when my App comes to the foreground. Using the NSMetadataQuery and searching in NSMetadataQueryUbiquitousDocumentsScope the shared documents are not displayed.
Any suggestions
Best
Reza
Try this:
let DOCUMENTS_DIRECTORY = "Documents"
if let iCloudDocumentsURL = NSFileManager.defaultManager().URLForUbiquityContainerIdentifier(nil)?.URLByAppendingPathComponent(DOCUMENTS_DIRECTORY)?.URLByAppendingPathComponent(fileName) {
if let pathComponent = iCloudDocumentsURL.path {
if (NSFileManager.defaultManager().fileExistsAtPath(pathComponent, isDirectory: nil)) {
}
}
}
This checks the file under the public Document directory.

Bundling a Realm with an App section

I am trying to bundle a realm file with my app. I followed steps 1-5, but I am not clear on step 6. It says to copy the file back to the document folder, especially if it will be edited by the users.
Assuming, the copy statement goes inside the AppDelegate (similar to how it is done in the sample migration code), and the file is copied, what will happen the next time the app is launched? will it copy the file again, and overwrite it? Am I missing something?
Do I need to run it once before I ship the app, and then remove the code from the app delegate?
EDIT: this question is regarding realm-swift
If you bundle Realm file with your app it will be stored inside your app's bundle in Resources directory which is not writeable (because of the code signing). So if you want your users be able to change this data you have to copy this file to some directory with write access. Application’s Documents directory seems to be a good choice and it's also a default directory for default Realm.Configuration.
If it's an initial data for your app and you don't want to overwrite it each time your app launches, you can simply check if destination file exists and not copy your initial database in this case.
You can do it like this:
let initialURL = NSBundle.mainBundle().URLForResource("initial", withExtension: "realm")!
let defaultURL = Realm.Configuration.defaultConfiguration.fileURL!
if (!defaultURL.checkResourceIsReachableAndReturnError(nil)) {
do {
try NSFileManager.defaultManager().copyItemAtURL(initialURL, toURL: defaultURL)
} catch {
// Handle error here
}
}
let realm = try! Realm()
Adding to the above answer. Looks like currently the correct version of checking if realm file exists is: defaultURL.checkResourceIsReachable()

How to hide SQLite db files so they are not seen in iTunes file sharing

I have file sharing enabled on my app and so, when you view the documents folder in iTunes, there are the Core Data sqlite files siting there just waiting to be fiddled with by the user.
I have found a few discussions on this but, surprisingly, no one seems to address the concerns I have.
Some say to 'move' them to the library folder - in a custom sub-directory, and another says just rename the files prefixing each with a period.
Both options sound lovely, but these are essential OS files!
So my first question is, if you do either of these things (with NSFileManager.defaultManager() - I presume), will the app just automatically find them afterwards? ...or is there a specific 'way' in which you do (either of) them ...so that the app finds them afterward?
Any responses, if you could demonstrate using Swift rather than Objective C, that would be appreciated! Thanks, :)
Figured it out:
To anyone wondering the same thing (which I have seen many doing),
In my app delegate > in the 'persistentStoreCoordinator' lazy variable,
I changed the following line of code:
let url = self.applicationDocumentsDirectory.URLByAppendingPathComponent("MyAppName.sqlite")
to the following two lines of code:
let library = NSFileManager.defaultManager().URLsForDirectory(.LibraryDirectory, inDomains: .UserDomainMask)[0] as NSURL
let url = libary.URLByAppendingPathComponent("MyAppName.sqlite")
This would be the syntax for Swift 3 / 4:
let library = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask)[0] as NSURL
let url = library.appendingPathComponent("MyAppName.sqlite")