MacOS: How/where do I store a programmatically created file within my app folder? - swift

Very new to MacOS development (as in, completely new). I've developed a MacOS app (SwiftUI / Swift) and now figuring out a very things related to deployment.
My app generates a text file, but I'd like to save it within the app's folder (or whatever it is called) - and not in a user specified folder, and read it when I want. I can read resources from the Assets.xcassets but I'd like to be able to save as well without making the user choose a location.
Is there a way I can write/read from the app folder (I'm struggling to explain as I'm very unfamiliar with this system)?

Yes you can create a directory inside your application support folder, name it with app’s bundle identifier or your company and store all files that are not accessible to the user there:
Use this directory to store all app data files except those associated
with the user’s documents. For example, you might use this directory
to store app-created data files, configuration files, templates, or
other fixed or modifiable resources that are managed by the app. An
app might use this directory to store a modifiable copy of resources
contained initially in the app’s bundle. A game might use this
directory to store new levels purchased by the user and downloaded
from a server. All content in this directory should be placed in a
custom subdirectory whose name is that of your app’s bundle identifier
or your company.
You should take some time and read the File System Basics documentation
do {
let applicationSupport = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!
let bundleID = Bundle.main.bundleIdentifier ?? "company name"
let appSupportSubDirectory = applicationSupport.appendingPathComponent(bundleID,isDirectory: true)
try FileManager.default.createDirectory(at: appSupportSubDirectory, withIntermediateDirectories: true, attributes: nil)
print(appSupportSubDirectory.path) // /Users/.../Library/Application Support/YourBundleIdentifier
} catch {
print(error)
}

Related

How to write to a file in a project while the project is running? [duplicate]

Very new to MacOS development (as in, completely new). I've developed a MacOS app (SwiftUI / Swift) and now figuring out a very things related to deployment.
My app generates a text file, but I'd like to save it within the app's folder (or whatever it is called) - and not in a user specified folder, and read it when I want. I can read resources from the Assets.xcassets but I'd like to be able to save as well without making the user choose a location.
Is there a way I can write/read from the app folder (I'm struggling to explain as I'm very unfamiliar with this system)?
Yes you can create a directory inside your application support folder, name it with app’s bundle identifier or your company and store all files that are not accessible to the user there:
Use this directory to store all app data files except those associated
with the user’s documents. For example, you might use this directory
to store app-created data files, configuration files, templates, or
other fixed or modifiable resources that are managed by the app. An
app might use this directory to store a modifiable copy of resources
contained initially in the app’s bundle. A game might use this
directory to store new levels purchased by the user and downloaded
from a server. All content in this directory should be placed in a
custom subdirectory whose name is that of your app’s bundle identifier
or your company.
You should take some time and read the File System Basics documentation
do {
let applicationSupport = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!
let bundleID = Bundle.main.bundleIdentifier ?? "company name"
let appSupportSubDirectory = applicationSupport.appendingPathComponent(bundleID,isDirectory: true)
try FileManager.default.createDirectory(at: appSupportSubDirectory, withIntermediateDirectories: true, attributes: nil)
print(appSupportSubDirectory.path) // /Users/.../Library/Application Support/YourBundleIdentifier
} catch {
print(error)
}

read/write database path using sqlite.swift

I'm just starting out trying sqlite.swift and databases with swift. I have prepared a database with tables and preloaded with data. I wish to select data and insert data from within the app.
The problem is that I don't understand where (in my project) to put my database.db file in order for the app to find it.
my connection code:
let path = NSSearchPathForDirectoriesInDomains(
.documentDirectory, .userDomainMask, true
).first!
do {
db = try Connection("\(path)/database.db")
} catch {
db = nil
print ("Unable to open database")
}
In terms of where this file should go, I would suggest the “application support directory”. See File System Programming Guide: Where You Should Put Your App’s Files, which says:
Put user data in Documents/. User data generally includes any files you might want to expose to the user—anything you might want the user to create, import, delete or edit. For a drawing app, user data includes any graphic files the user might create. For a text editor, it includes the text files. Video and audio apps may even include files that the user has downloaded to watch or listen to later.
Put app-created support files in the Library/Application support/ directory. In general, this directory includes files that the app uses to run but that should remain hidden from the user. This directory can also include data files, configuration files, templates and modified versions of resources loaded from the app bundle.
Also see the iOS Storage Best Practices video.
The above is for iOS. If inquiring about macOS, see File System Programming Guide: The Library Directory Stores App-Specific Files.
But regardless of the OS, the technique for referencing the application support directory is largely the same:
do {
let fileURL = try FileManager.default
.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent("database.db")
db = try Connection(fileURL.path)
} catch {
db = nil
print("Unable to open database: \(error)")
}

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.

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

Swift Filled plist is empty next time I open up the application

When I'm opening up my application for the first time I have an empty plist that I fill with some values (writeToFile). I can read from the plist dynamically and get those values I just put into it. This far this good. Then I close my application and later on I open it again. At this point I want my plist to contain those values I dynamically wrote to it the previous time I opened the application, but to my disadvantage it's empty.
Is this a normal behaviour? Can I not use the plist as a "local database" where I save values dynamically, and read them some other time when the application is opened?
Thanks!
Files in "supporting files" are not supposed to be modified.
The files of the supporting files group are (like all other non-code files) copied into the app bundle. Apple says about the app bundle:
This directory contains the app and all of its resources.
You cannot write to this directory. [...] Writing to this directory changes the signature and prevents your app from launching. You can, however, gain read-only access to any resources stored in the apps bundle.
If you want to store users preferences, selection, etc. you should create the plist file in the documents directory.
Use this directory to store user-generated content. The contents of this directory can be made available to the user through file sharing; therefore, his directory should only contain files that you may wish to expose to the user.
The contents of this directory are backed up by iTunes
You can get the path to the documents directory using:
NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).last as! String
If you want to store preferences you may also want to take a look at NSUserDefaults.
https://developer.apple.com/library/ios/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html