How can local settings be stored on apple watch? - swift

I am aware of the methods listed here to save preferences across Apple Watch and iOS.
But, they mention that settings cannot be changed on the Apple Watch side, and that a WCSession would be needed to change settings from the watch.
I am looking for a method to store preferences locally on the watch. These preferences are just for the watch (so a shared preferences scheme is not what I'm looking for). Also, the method needs to work with or without the phone present.
My end goal is just to have switches on my Apple Watch app retain their state once the user changes them on the watch. I want their state to be retained if the app is closed and reopened.
Any ideas on how to do this? My only idea so far is to save a file locally to the watch and read from that on launch, but I feel like there must be a simpler way.
EDIT: I have since realized that even though Apple discourages the setting of preferences on the watch, it is completely possible (UserDefaults can be used EXACTLY as it is in iOS). This allowed me to have local watch settings. Then, if settings need to be transferred between the phone and the watch, Watch Connectivity (specifically, TransferUserInfo) can do the job.

UserDefaults is just a file-backed dictionary. The file is stored as a plist and UserDefaults is essentially built on top of PropertyListSerialization. For a simple setup, you can roll your own defaults. File storage on the watch is basically the same as in iOS:
Data placement. WatchKit extensions must take an active role in managing your data. The container directory for your WatchKit extension has the same basic structure as the container for your iOS app. Place user data and other critical information in the Documents directory. Place files in the Caches directory whenever possible so that they can be deleted by the system when the amount of free disk space is low.
let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
guard let url = urls.first else {
return
}
// The types that can be stored in this dictionary are the same as what NSUserDefaults can do
let preferences = [ "myDefault" : true ]
if let data = try? PropertyListSerialization.data(fromPropertyList: preferences, format: .xml, options: 1) {
do {
try data.write(to: url.appendingPathComponent("mypreferences.plist"))
} catch {
print("Failed to write")
}
}
if let input = try? Data(contentsOf: url.appendingPathComponent("mypreferences.plist")),
let dictionary = try? PropertyListSerialization.propertyList(from: input, options: .mutableContainersAndLeaves, format: nil),
let d = dictionary as? [String : Bool],
let value = d["myDefault"] {
print(value)
}
Alternatively
To share the preferences, you can use the solution here.

Related

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

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

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 handle "open in" retrieved file with swift 2

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)