Shared User Defaults via App Groups in iOS 11 not working - ios

Did anyone face an issue with Shared User Defaults via App Groups on iOS 11? I am saving a value in one of the extensions but I am not able to fetch same value via another extension.
In the first extension :
let defaults = UserDefaults.init(suiteName: Constants.commonSuite)
defaults.set("Sample", forKey: "SampleKey")
defaults.synchronize()
In the second extension :
let defaults = UserDefaults.init(suiteName: Constants.commonSuite)
let sampleString = defaults.object(forKey: "SampleKey")
print(sampleString)
Interestingly this thing works fine on iOS 10. Breaks on iOS 11

Had the same issue.
Suitename needs to be the same value as your app group name, not some arbitrary value.
Hope that helps!

Related

Swift - UserDefaults setting not getting saved inside framework

I have a framework that is generating a device UUID once and saving it using UserDefaults. The app has access to the UserDefaults and everything works as expected. However, the framework is not accessing UserDefaults in some cases.
I sorted this out on an iPhone 8 using the synchronize() method:
func getDeviceID() -> String {
if let device = UserDefaults.standard.object(forKey: "DeviceID") as? String {
return device
} else {
let device = UUID().uuidString
UserDefaults.standard.set(device, forKey: "DeviceID")
UserDefaults.standard.synchronize() // this line helped with an iPhone 8
return device
}
}
However, on an older iPhone SE 1st generation the issue comes back.
First, why is this happening at all, and why is the synchronize() method seemingly helping in a newer device? (Both phones are running iOS 13)
Are there any known limitations when accessing UserDefaults from within a framework?
If it is failing when you're reading data right after writing the deviceId to UserDefaults
Then it could be related to how UserDefaults actually stores the data to disk.
The actual write to disk is asynchronous and batched automatically by NSUserDefaults.
Check this
So there's a chance that it is slower for older devices running new iOS versions.

How To Use Shared Container/App Groups To Access Files From Other Targets in Swift

I am new to Swift.
I am trying to make a two-app project, where one app contains some data files and the other accesses those files.
The solution, I think, has been to use the App Groups entitlements to allow for this, and access the files through those means. I have been able to follow along with the example here: Communicating and persisting data between apps with App Groups. In particular, the 2nd answer, which is Swift-ish (maybe an older version I guess?). It does seem to work with the right entitlements. So, now the question is how can I access the file from one app, with it being apart of the another? I'm not familiar with the API's and correct functions that I can use (new to Swift, as I said).
The apps are basic. Setup as single view applications, with everything defaulted except the ViewController's, entitlements, and one has the test data. The Xcode project structure is:
testingData/
testingData/
testingData.entitlements
TestData/
testdata.txt
AppDelegate.swift
ViewController.swift
Main.storyboard
Assets.xcassets
LaunchScreen.storyboard
Info.plist
sharedContainerTest/
sharedContainerTest.entitlements
AppDelegate.swift
ViewController.swift
Main.storyboard
Assets.xcassets
LaunchScreen.storyboard
Info.plist
Products/
testingData.app
sharedContainerTest.app
The entitlements are both the same. They each have App Groups enabled, with the same string: group.com.example.name. On the testingData target, the ViewController has the following chunk in the viewDidLoad function from that example (modified for Swift 4.x):
var userDefaults = UserDefaults(suiteName: "group.com.example.name")!
userDefaults.set("user12345", forKey: "userId")
userDefaults.synchronize()
On the sharedContainerTest target, its ViewContoller has
var userDefaults = UserDefaults(suiteName: "group.com.example.name")
if let testUserId = userDefaults?.object(forKey: "userId") as? String {
print("User Id: \(testUserId)")
}
in its viewDidLoad function. As I said, this seems to work, but now what do I need to add to this to access the testdata.txt file from the testingData app? Does it need to be stored as a variable, perhaps? Or is there a Bundle object that can do this?
If this has been answered elsewhere, please kindly point me to it and I'll take this down.
After some trial and error, the answer is as follows:
Instead of passing in the string "user12345", you need to pass in the URL to the file you want to read for the userDefaults.set method as follows:
var userDefaults = UserDefaults(suiteName: "group.com.example.name")!
userDefaults.set(Bundle.main.url(forResource: "testdata", withExtension: ".txt"), forKey: "userId")
userDefaults.synchronize()
Then in the receiver app, you call that object and set the URL:
let userDefaults = UserDefaults(suiteName: "group.com.example.name")
let test = userDefaults?.object(forKey: "userId") as? String
let testURL = URL(fileURLWithPath: test!)
From here you can read in the contents as normal.

Is sharing UserDefaults between iOS and tvOS still possible?

Is sharing UserDefaults between iOS and tvOS still possible?
In my Xcode project I use UserDefaults to share data between my iOS target and my tvOS target. I always receive nil values from my UserDefaults when I try to get data back from my tvOS app.
These are the steps I took to share data:
1: Add App Groups for both targets. Both targets use the same App Group ID:
group.nl.mycompany.myappname.
I use the .nl domain but this should be fine since this also worked for my other projects.
2: Confirm both targets have the same deployment target. I tried using 10.0 and 11.0.
3: Validate the myproject.entitlements that everything is set OK.
4: Validate that on developer.apple.com the App Group is enabled for my bundle identifier.
5: Both targets have the same bundle ID. I also tried using 2 different bundle identifiers.
6: The way I write to UserDefaults from my iOS app:
guard let defaults = UserDefaults(suiteName: "group.nl.mycompany.myappname") else { return }
defaults.set("Apple", forKey: "username")
defaults.synchronize()
I confirm this works in my iOS app by getting the value like so:
guard let defaults = UserDefaults(suiteName: "group.nl.mycompany.myappname") else { return nil }
defaults.synchronize()
let name = defaults.string(forKey: "username")
This indeed returns "Apple".
7: Opening my tvOS app and calling this code returns nil:
guard let defaults = UserDefaults(suiteName: "group.nl.mycompany.myappname") else { return nil }
defaults.synchronize()
let name = defaults.string(forKey: "username")
Is it possible that UserDefaults sharing has been removed? Something similar happened to sharing UserDefaults between your phone and watch link here. I also read that the maximum size of UserDefaults is 500kb for the AppleTV but saving this simple string should be fine.
Apple clearly states in the UserDefaults documentation that
With the exception of managed devices in educational institutions, a
userโ€™s defaults are stored locally on a single device, and persisted
for backup and restore. To synchronize preferences and other data
across a userโ€™s connected devices, use NSUbiquitousKeyValueStore
instead.
As it says, you should use iCloud-based NSUbiquitousKeyValueStore for synchronized data storage.
As for its (NSUbiquitousKeyValueStore) limits, the documentation says
The total amount of space available in your appโ€™s key-value store, for a given user, is 1 MB. There is a per-key value size limit of 1 MB, and a maximum of 1024 keys.

iPhone 7 NSUserDefaults turning into nil

I have an iPhone 7 , and I also use it for development. I noticed that in my app , I was storing a session token for auto login purposes into the NSUserDefaults. But sometimes , without removing it the session token was nil and the auto login is not working. This also happens to other apps , where some times it asks me to login again , although I already have. I am guessing that somehow to NSUserDefaults that most of the apps use are being erased somehow. Anyone having a similar issue ?
class func setSessionToken(_ token: String){
let defaults = UserDefaults.standard
defaults.setValue(token, forKey: sessionTokenKey)
defaults.synchronize()
}
class func getSessionToken()->String?{
let defaults = UserDefaults.standard
return defaults.string(forKey: sessionTokenKey)
}
This issue normally occurs when Running an iOS8 or 9 simulator followed by an iOS10 simulator and NSUserDefaults stop working in the simulator.
But, in your case if it is not working in iPhone also . You can try it with latest version of Xcode8 .

Access most recently used emojis list in iOS

In iOS, in the native Emoji keyboard, you can see your most recently used Emojis. I would like to know if it is possible to get the data on those Emojis (which is app-independent) from inside my app.
My goal is to display the most used emoji, given a user, in my app.
If you just want an Emoji selector you could use/modify libraries like SYEmojiPopover or AGEmojiKeyboard which allows you to have full control on the output without messing with the iOS internals (albeit the "recents" list will be app-specific).
On iOS 9 the preferences are stored in the com.apple.EmojiPreferences suite, which you could extract the list of most recently used emoji by:
// swift 3:
let prefs = UserDefaults(suiteName: "com.apple.EmojiPreferences")!
let defaults = prefs.dictionary(forKey: "EmojiDefaultsKey")!["EmojiRecentsDefaultsKey"]! as! [String: Any]
let recents = defaults["RecentsKey"]! as! [String]
print(recents)
// swift 2:
let prefs = NSUserDefaults(suiteName: "com.apple.EmojiPreferences")!
let recents = prefs.dictionaryForKey("EmojiDefaultsKey")!["EmojiRecentsDefaultsKey"]!["RecentsKey"]! as! [String]
print(recents)
// prints e.g. ["๐Ÿ”", "๐Ÿšณ", "๐Ÿšฟ", "โŒ›", "๐Ÿ‘ถ", "๐Ÿ‡ฟ๐Ÿ‡ฆ", "โ›ช", "๐Ÿš†", "๐Ÿš…"]
Note that this is UNDOCUMENTED, and I have only checked it works on iOS 9 when deployed via Xcode. There is no guarantee that the App Store reviewers will allow this usage, nor there is any guarantee that it will work in the past or future versions.

Resources