Storing data in custom keyboard - objective-c

I am building a custom keyboard that learns the way you type for smarter auto-correct. In order to learn... I need to be able to STORE data to the users device. I've tried using NSFileManager with NSDocumentsDirectory but nothing is getting saved int he AppExtension. (I tested the code (copy paste) in a regular app (non-app-extension) and it worked). I even enabled "Requests Open Access" in the .plist and re-installed the keyboard... still wouldn't save data.
Is there a way to store data in an app-extension?
Possible solutions I've pondered:
•Maybe creating a contact in the users address book that has my info in it (if app-extensions are allowed to do that), but a user might be suspicious as to why my app is requesting permission to modify their contact's address book).
•Displaying a hidden UIWebView that uses javascript injection to store and read data Safari Javascript Database, but I'm afraid this data might be erased if cache is ever cleared.

edit: now this is no longer working for me on iDevice beta 5 (with requestOpenAccess enabled OR disabled)? But it still works on SIMULATOR beta 5? Hmmm
__ original post below: _
It turns out NSUserDefaults DOES save data, it just does not work on BETA 3 simulator, but did work on BETA 5 iDevice. RequestsOpenAccess does NOT need to be enabled!
NSUserDefaults *defaults = [[NSUserDefaults alloc]initWithSuiteName:#"com.company.keyboard.uniqueCodeHere"];//uniqueCodeHere can be anything... we just have to make sure the SuiteName isn't equal to your info.plist bundle ID so we add a random uniqueCode to the end of this ID.
[defaults setObject:#"myStringData" forKey:#"savedStrings"];
[defaults synchronize];
*Note, SuiteName Can NOT equal bundle ID in info.plist or it doesn't work for some reason...

Related

Fetch preferences / permissions for NSUserNotificationCenter

I'm currently trying to set up a notification service using the old NSUserNotification API on macOSX (not iOS) in objective c++.
The catch is that I would need to know the system preferences associated with notifications that the user set (and preferably during the rest of runtime too) to determine if my notifications are actually seen. The application is meant to get the users attention in some way to guarantee things like reminders don't go unnoticed, so the thing I want to avoid is a notification delivered that silently doesn't produce any sound nor image on the screen whatsoever without the application knowing. If the notification is not allowed the application should handle it in some other way.
I am working on a macOS 10.14 Mojave machine. My overall code with NSUserNotificationCenter works fine with a custom Delegate that implements shouldDeliver, didActivate etc. Its just that I am currently assuming the user didn't press the "frick off" button on my notifications.
Unfortunately, NSUserNotificationCenter and related classes don't appear to feature a function that determines the permissions the way the new API (UNUserNotificationCenter) does. I have been trying to resolve this for a few hours so I was wondering if anyone else has found a solution to this. I can't really use the new API since this is strictly for back-compatibility in favour of the possibly rather prevalent amount of people who don't use mojave yet.
Things I have tried to make it work so far:
Try and see if there is a .plist somewhere where these settings are stored, in the User-specific Library folder as well as the general Library folder.
Try and find said .plist using CFPreferencesCopyKeyList and related methods. Apparently I don't really know the right domain name.
This answer from 2012 which appears to be outdated since I cannot locate the database on my machine anymore. Might be due to me using a mojave machine, or the file has since moved somewhere else.
Try and determine whether we can retro-actively check our permissions by checking a NSUserNotification's "presented" property on didDeliverNotification, shouldPresentNotification in the delegate. Unfortunately as stated in NSUserNotificationCenter itself it still behaves the same whether the preferences allow notifications or not.
Some code I tried includes the following:
CFPreferencesCopyKeyList((CFStringRef) #"com.apple.systemPreferences.plist", kCFPreferencesCurrentUser, kCFPreferencesCurrentHost);
CFPreferencesCopyKeyList((CFStringRef) #"com.apple.systemPreferences.plist", kCFPreferencesAnyUser, kCFPreferencesAnyHost);
//Unfortunately returns null with any combination of com.apple.notificationcenter.plist, kCFPreferencesCurrentUser, kCFPreferencesCurrentHost
CFPreferencesCopyKeyList((CFStringRef) #"com.apple.notificationcenter", kCFPreferencesAnyUser, kCFPreferencesAnyHost);
On my system (only macOS High Sierra), it looks like the file you're after is:
~/Library/Preferences/com.apple.ncprefs.plist
That would correspond to a combination of kCFPreferencesCurrentUser, and kCFPreferencesAnyHost. (For the record, kCFPreferencesCurrentUser + kCFPreferencesCurrentHost equates to pref files in the ~/Library/Preferences/ByHost/ folder).
I don't think any settings would be stored in the local domain (by that I mean in the root /Library/Preferences/ folder).
I was able to get a list of apps and settings using the following code:
[[NSUserDefaults standardUserDefaults] addSuiteNamed:#"com.apple.ncprefs"];
NSArray *apps = [[NSUserDefaults standardUserDefaults] objectForKey:#"apps"];
NSLog(#"[%# %#] apps == %#", NSStringFromClass([self class]), NSStringFromSelector(_cmd), apps);
NOTE: this will not work if your app is sanboxed, as access to other pref files like that will be denied.

Values not always persisted in App group between companion app & app extension

From time to time, but not always (I have had this working for a bit), the app/extension gets in a state where I can't read a flag set in my App Group between my companion app and my app extension. Don't know how it gets in this state or why the values differ, but it's critical to my application these always be in sync.
Companion app viewDidLoad:
NSUserDefaults *myAppSettings = [[NSUserDefaults alloc] initWithSuiteName:#"group.myapp"];
.....
[myAppSettings setBool:true forKey:#"myBool"];
[myAppSettings synchronize];
NSLog([myAppSettings boolForKey:#"myBool"] ? #"Companion app - bool TRUE" : #"Companion app - bool FALSE");
App extension viewDidLoad
NSUserDefaults *myAppSettings = [[NSUserDefaults alloc] initWithSuiteName:#"group.myapp"];
[myAppSettings synchronize];
NSLog([myAppSettings boolForKey:#"myBool"] ? #"App extension app - bool TRUE" : #"App extension - bool FALSE");
Console output
Companion app - bool TRUE
App extension - bool FALSE
I also synchronize before my companion app will enter background. I have my app group set up in the portal etc.
What am I doing wrong?
EDIT
Apparently others having this problem too:
https://devforums.apple.com/message/977151#977151
"I think that this is currently very glitchy.
Sometimes the data sharing works, then a change and all of a sudden the widget can't see the shared data anymore (both on Simulator and device).
Annoying and hope it's a bit more reliable in next beta!"
EDIT 2
Looks like another person has reported this exact issue as well:
"I also noticed the same thing too.This not only happen to the
NSUserDefaults, but also all the files in the container folder. The
keyboard extension suddenly will lose read/write pemission to the
container folder after using the keyboard for a while."
EDIT 3
More evidence: https://devforums.apple.com/message/1028078#1028078
After I upgrade to beta 3, I noticed that sometimes the keyboard
failed to open the database because it failed to access to the DB
file. The keyboard has been able to access to the file before.
EDIT 4
Seems like this could be because the keyboard loses the RequestsOpenAccess flag. But I can't reproduce it, and there's no way for me to tell for sure.
EDIT 5
Seems like others are reporting this in the iOS8 GM build:
This issue still persists for me in the GM. It seems related to a
keyboard crash.. but also there seems to be some contention between
keyboard and containing app in terms of who creates the suite in what
order. I think this problem is on Apple's end. Trust me, I WANT it to
be my fault but I've spent countless hours with trial and error. No
matter what I do in code and verify with NSLog, it will end up in this
state eventually. Hoping someone finds a magic pill. :S
Has anyone solved this yet?
You must request open access in order to access shared NSUserDefaults. It's stated directly in the App Extension Programming guide:
By default, a keyboard has no network access and cannot share a container with its containing app. To enable these things, set the value of the RequestsOpenAccess Boolean key in the Info.plist file to YES.
Be sure you change the RequestsOpenAccess field to YES. You'll find it in keyboard's Info.plist > NSExtension > NSExtensionAttributes > RequestOpenAccess. Then remove the keyboard in Settings, delete the app, run it again, and add the keyboard again. After you add it, tap on the keyboard name and then flip the switch to enable Allow Full Access. You'll need to instruct the users to follow those same steps to grant access (and reassure them you're not evil), otherwise it simply will not work and you'll never get the data that's stored in your shared container. Note that in iOS 8.3+, if the user hasn't enabled full access the keyboard will be able to access the shared container, but writing to it will not save the data, for security and privacy purposes. In 8.2- you can't access that data without open access granted.
I can confirm that the problem is related to RequestsOpenAccess flag.
Assuming that everything done right (NSUserDefaults use initWithSuiteName, all Capabilities for main application and custom keyboard were set, etc.) I have the next steps:
1) Install the main application and a custom keyboard on device
2) Set 'Allow full access' for the custom keyboard to YES
3) Add some items (in my case this is a simple text templates) in the main app
4) Go to keyboard and check that all items, that were added from the main app,
appeared in custom keyboard
5) Go to main app and add a few more items
6) Go to keyboard and now you will see that nothing changed
7) Go to settings and switch 'Allow full access' to NO and then to YES
8) Go to custom keyboard again and check that item which were added in step 5 appeared.

Xcode 5.0 NSUserdefaults still get stored preferences after deleted the plist

I got a problem about programming an osx application on Xcode 5 while using the NSUserDefaults. Usually, we just use [[NSUserDefaults standardUserDefaults] setObject:#"This is an object" forKey:#"Test"] to remember a user preference. After that, the application will generate a plist file at ~/Library/Preferences/application.bundle.identifier.plist.
The problem is, after I deleted the plist file, the application could still get the preferences I stored. There is no way to clear that plist even if I tried to clean project, restart xcode, delete the files in derived folder. The only way for me to solve this problem is to restart the system, so I guess there is something stored in the memory. The question is how can I clear these stored preferences? (I don't think it's convenient to clear the preferences by adding code manually in debugging and testing.) And I tried the former version of Xcode 4.x, there's no such problem. Anyone has interest could just create a new cocoa project, and add the code like:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:#"This is an object." forKey:#"Test"];
NSLog(#"%#", [defaults objectForKey:#"Test"]);
under "applicationDidFinishLaunching". Then go and delete ~/Library/Preferences/application.bundle.identifier.plist.
After that, comment the line: [defaults setObject:#"This is an object." forKey:#"Test"];
in your code and run the application again. The console will still show "This is an object."
My environment is Mavericks GM and Xcode 5.0(5a1413).
Hope this is not something just only happened to me and appreciated any help!
This is an OS X issue not directly related to the version of Xcode you are using. Apple's official line is that deleting the plist file to remove preferences has never been officially supported, and in more recent OS X releases it is unreliable due to changes in the way the preferences are stored.
The supported way to remove preferences is to use the defaults command at the terminal, e.g.:
defaults delete application.bundle.identifier
The defaults command can also remove/change individual settings with in the preferences. For full details see man defaults.

Force 'Shared User Defaults Controller' to save to disk immediately?

I have a preferences pane that uses the Shared User Defaults Controller, which reading and saving preferences a piece of cake. It seems though that when changes are made to the fields, they aren't immediately saved to the plist fie. This creates a problem when my application needs to re-read the file immediately after the change has been made and the plist still hasn't been updated.
How can I force the preferences pane to update the preferences file immediately?
This will automatically save any change you do right away to disk:
NSUserDefaultsController *controller = [NSUserDefaultsController sharedUserDefaultsController];
[controller setAppliesImmediately:YES];
If you need this only in specific cases, you can also use and save some of the expensive I/O (you really should try to let the cache mechanism cache as much as possible instead of writing everything right away to disk):
NSUserDefaultsController *controller = [NSUserDefaultsController sharedUserDefaultsController];
[controller save:self];
Also, are you sure that you are trying to solve the right problem? You can always get the up to date version of the user defaults by querying NSUserDefaults where you don't need to care about wether the current version is cached in RAM or already written to disk.
Per the docs:
[userDefaults save:self];
or
[userDefaults setAppliesImmediately:YES];

Where is a Mac Application's NSUserDefaults Data Stored?

I am using NSUserDefaults to store some data in my application.
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
[prefs setObject:#"dummy string" forKey:#"lastValue"];
[prefs synchronize];
For testing purposes I need to see the System Preferences plist file where my NSUserDefaults data is saving on the Mac.
I know where the iOS application user defaults are stored, but I don't know about mac application. Where is a Mac Application's NSUserDefaults Data Stored?
They can be found in more than one place:
~/Library/Preferences/com.example.myapp.plist
~/Library/SyncedPreferences/com.example.myapp.plist
and if sandboxed
~/Library/Containers/com.example.myapp/Data/Library/Preferences/com.example.myapp.plist
~/Library/Containers/com.example.myapp/Data/Library/SyncedPreferences/com.example.myapp.plist
In ~/Library/Preferences/com.example.myapp.plist.
(Xcode 7.3.1,macOS 10.11.6)
For Additional,if you are using App Groups
if let prefs = NSUserDefaults(suiteName: "group.groupApps") {
...
}
plist file will be here:
~/Library/Group Containers/group. groupApps/Library/Preferences/group.groupApps.plist
On Sierra, I found the data here: ~/Library/Application Support/.
One more possible location for these data comes into play when trying things out in a Playground. I was experimenting with UserDefaults in a Playground, using XCode 8.3 and Swift 3, and wanted to see the resulting plist file. After some detective work (UserDefaults files have the bundle identifier in the filename and calling Bundle.main.bundleIdentifier in a Playground gives the XCode identifier) I found to my great surprise that the UserDefaults data was added to:
~/Library/Preferences/com.apple.dt.Xcode
In other words, keys and values are added to the XCode preferences file! I double-checked by coming up with very unlikely strings for the keys and they were indeed added there. I did not have the courage to try using some keys that were already in use by XCode but caution seems good here.