1

For the main app, I can permanently store a key by using UserDefaults.standard

in appDelegate I have:

let defaults = UserDefaults.standard
let defaultValue = ["userUID" : ""]
defaults.register(defaults: defaultValue)

then, when user registers I have:

let defaults = UserDefaults.standard
defaults.set(user!.uid, forKey: "userUID")
defaults.synchronize()

and when I want to read it, it's perfect. However, I'm not exactly sure how to pass this "userUID" upon registration to the custom keyboard extension, and permanently save it there.

I know I can pass data using app groups, and having:

let defaultsToKeyboard = UserDefaults.init(suiteName: "group.com.app")

but this gets erased on every new instantation. Looking for a swift 3 solution.

VDog
  • 1,083
  • 2
  • 13
  • 35
  • 2
    Is the data you wish to store really confidential? If it is, you should look into [keychain](https://developer.apple.com/library/content/documentation/Security/Conceptual/keychainServConcepts/01introduction/introduction.html), which is a lot more secure and the data can be shared between your apps. – Papershine Feb 18 '17 at 08:49
  • It's not confidential, but if keychain is an easy alternative I can consider it. It's just hard to believe that there is a UserDefaults.standard, which perma stores data until app is deleted, but you can't pass this perma data to an extension. – VDog Feb 18 '17 at 08:51
  • I think I'm also not fully understanding the "init(suieName)" data passing, because it seems the main app has to be open and those lines have to run for data to pass, but then when keyboard opens later, how does it read it? Is it stored somewhere as an instance, and once custom keyboard launches that data is then passed, or permanently stored? No appDelegate for extensions which makes things tricky. – VDog Feb 18 '17 at 08:54
  • Yes, so maybe you should use keychain – Papershine Feb 18 '17 at 08:59
  • I would prefer to learn and fully understand the process than to jump to another solution. It is working right now, so something tells me the init suiteName might be permanent, however haven't extensively tested, and would love an answer that explains in detail the communication that is occurring. – VDog Feb 18 '17 at 09:14

1 Answers1

3

Here's how to set and read UserDefaults between two apps:

  1. Select your project in the project navigator
  2. Select the main app target and go to the capabilities tab
  3. Turn on "App groups"
  4. Create a new container that starts with “group.”, so give it a name like “group.foo.bar”.
  5. Select your Keyboard Extension target and switch on app groups again. Don’t create a new container, rather select the created container (group.foo.bar) to signify that the Keyboard Extension is a member of the container.

This is how you write to the UserDefaults:

let defaults = UserDefaults(suiteName: "group.foo.bar") //replace suiteName with your container name
let defaultValue = ["userUID" : ""]
defaults!.register(defaults: defaultValue)
defaults!.set("a", forKey: "userUID")
defaults!.synchronize()

And how to read from the keyboard extension:

let defaultsToKeyboard = UserDefaults(suiteName: "group.foo.bar") //replace suiteName with your container name
let uid = defaultsToKeyboard?.object(forKey: "userUID")
print("\(uid)")

Basically this allows apps that is part of the group to access the same UserDefaults. UserDefaults are not destroyed even after the containing app is deleted. UserDefaults are always saved to disk, so it will never be destroyed. When you use app sharing, your apps inside the group are actually accessing the same UserDefaults. That's why there is a synchronize function, it saves the UserDefaults to disk and also why it is unsafe, anybody who can see the underlying file system can look at its contents.

Your extension can also 'see' the UserDefaults. It just can't access it. Creating group ids tells iOS that your extension (or even app) is authorized to access it, so it can read and write data to and from it.

Your app is not needed to be in the foreground to access the UserDefaults as it is saved on disk already!

Remember that UserDefaults are saved to disk and not stored in an instance!

Papershine
  • 4,995
  • 2
  • 24
  • 48
  • This applies to all different types of UserDefaults? both standard and innit(suiteName:String)? This is a really excellent answer by the way. I already had this working, but it clarifies some confusion. – VDog Feb 18 '17 at 13:18
  • the only difference I have is UserDefaults.init(suiteName: "group.com.foo") – VDog Feb 18 '17 at 13:18
  • Yes, all UserDefaults are written to disk – Papershine Feb 18 '17 at 14:13
  • How would I save permanent variables that only need to be seen in the custom extension? For the main app when I did it, I used the main appDelegate, but since extension doesn't have mainApp, I guess I can do this same way? – VDog Feb 21 '17 at 04:57
  • The OS invokes `widgetPerformUpdateWithCompletionHandler:` when the extension is loaded. Use a group id (eg group.foo.bar) to save it, or create a new group id just for the extension see [here](http://stackoverflow.com/questions/24064722/save-and-load-data-on-today-extensions-ios-8) for more details. – Papershine Feb 21 '17 at 05:04
  • where is this invoked? is this an override of a method? I guess I can do NSUSERDEFAULTS in viewDidLoad, but I'm worried it would continuously set the initial value to whatever I originally set it to. – VDog Feb 23 '17 at 03:30
  • I created a new group in my extension, and made a function that I ran in viewDidLoad, then later read the data. if function runs it works fine, but if I comment function out (after running it the time before), and then try to get data, I get nil. So I guess I can't do this in viewDidLoad? – VDog Feb 23 '17 at 03:55
  • `widgetPerformUpdateWithCompletionHandler` is part of `NCProvidingControl`, see here for [documentation](https://developer.apple.com/reference/notificationcenter/ncwidgetproviding). Conform to it and override it. It gives opportunities to let the OS update the widget. – Papershine Feb 23 '17 at 04:38