UIKit UserDefaults in SwiftUI widget - swift

Hopefully a simple question, but I'm wondering how I would access UserDefaults data saved in my UIKit app from a SwiftUI Widget? I need to display some of this data in a widget.
Thanks!

You need to use UserDefaults(suiteName:) instead of UserDefaults.standard along with an AppGroup. UserDefaults.standard is only accessible in the app that it is in, it is not available to any of the extensions or other apps that you may make. This is why you have to use an AppGroup.
Once you have created your AppGroup (you can do this in the Signing and Capabilities section) you should have a suiteName for it, something like:
group.com.my.app.identifier
Then in your UIKit part of your app you can set the values in the AppGroup's UserDefaults in the following way:
if let userDefaults = UserDefaults(suiteName: "group.com.my.app.identifier") {
userDefaults.setValue("value to save", forKey: "Key")
}
And reading them back you can use:
if let userDefaults = UserDefaults(suiteName: "group.com.my.app.identifier") {
let value = userDefaults.string(forKey: "Key")
}
As the Widget will be written in SwiftUI you can use the property wrapper #AppStorage to access the values:
#AppStorage("Key", store: UserDefaults(suiteName: "group.com.my.app.identifier"))
var value: String = ""
If you have already stored the values that you wish to use in UserDefaults.standard you will need to copy them across to the UserDefaults(suitName:).

Related

SwiftUI: Can you use AppStorage with decoded json?

I have a list of countries and a field on them is favorite. If a user toggles the favorite button, that country's bool value of favorite is true. Elsewhere in my code, I use favorites to filter through all the countries and have a separate view that just shows the favorite countries.
#State var countries: [Country] = Bundle.main.decode("Countries.json")
var favorites : [Country] {
return countries.filter { $0.favorite }
}
I want to persist the countries so that I can reload the favorites view on subsequent app visits. Can I use #AppStorage for this or am I going about this the wrong way? CoredData seemed a bit like overkill when I was looking into it. I am new to SwiftUI coming to Javascript so apologies if this seems trivial.
In your case optimal will be to use UserDefaults
Use this part of code in func() or init()
For saving data in UserDefaults:
if let encoded = try? JSONEncoder().encode([Country]) {
UserDefaults.standard.set(encoded, forKey: "Countries")
}
For loading data from UserDefaults:
if let data = UserDefaults.standard.data(forKey: "Countries") {
if let decoded = try? JSONDecoder().decode([Country], from: data) { }
}
p/s
And I would like to recommend SwiftUI Tutorial from Apple if you are beginner

When do we need to use JSONEncoder and JSONDecoder with UserDefaults

To save some data to UserDefaults first we must encode it as JSON using JSONEncoder, which will send back a Data instance we can send straight to UserDefaults.Then reading saved data is a matter of converting from Data using a JSONDecoder. But sometimes we dont have to do that.
My question is will that method work anytime and when do i have to use it because i found this other solution without encode and decode:
var allWords = [String]()
var usedWords = [String]()
var currentWord: String?
In viewDidLoad:
let defaults = UserDefaults.standard
if let presentWord = defaults.object(forKey: "presentWord") as? String,
let savedWords = defaults.object(forKey: "savedWords") as? [String] {
title = presentWord
currentWord = presentWord
usedWords = savedWords
print("Loaded old game!")
Save method:
func save() {
let defaults = UserDefaults.standard
defaults.set(currentWord, forKey: "presentWord")
defaults.set(usedWords, forKey: "savedWords")
}
It's simple and faster way but Im not sure when i can use it with no worries
UserDefaults storage is a property list. NSString, NSData, NSArray, and NSDictionary are the only Cocoa classes that can be expressed directly in a property list. Moreover, an NSArray or NSDictionary can be expressed in a property list only if its elements are instances of those classes, along with NSDate and NSNumber. Those are the property list types.
If your Swift type bridges to a property list type, you can store it directly. So String will bridge to NSString, and an array of String will bridge to an NSArray of NSString, so you can store them directly.
But if what you've got is not a property list type, you need to transform it into a property list type before you can store it, and the usual solution is to transform it into an NSData (Swift Data). You don't have to use JSONEncoder for that but you do need to do it somehow.

Retrieve an image from UserDefaults

I am able to successfully save my image in user defaults as I use my app and retrieve it just fine but am having trouble figuring out how to retrieve it in a simple via init(). In my example it will returns the AppValue.avatar (my default image) but won't return the stored image. It can't find the previously stored image so it substitutes my default image it fails because I have UIImage in the published variable. I think it must have to be retrieved as data but if I change UIImage in the init() to Data Xcode is not happy.
class UserSettings: ObservableObject {
#Published var avatar: UIImage {
didSet {
/// Convert to data using .pngData() on the image so it will store. It won't take the UIImage straight up.
let pngRepresentation = avatar.pngData()
UserDefaults.standard.set(pngRepresentation, forKey: "avatar")
printSave(forKey: "avatar", value: avatar)
}
}
init() {
self.avatar = UserDefaults.standard.object(forKey: "avatar") as? UIImage ?? AppValue.avatar
}
}
It's simply a matter of keeping mental track of what type a thing is.
You saved the image as its pngData (correctly). This is not a UIImage; it is a Data. Your pngRepresentation, which is what gets saved, is a Data.
Hence when you retrieve the image and say as? UIImage, that test fails. It is not a UIImage. It's a Data.
Therefore, fetch data(forKey:) (or say as? Data instead of as? UIImage). Now you have the Data. then call UIImage.init(data:) to retrieve the image.

Retrieving array of custom objects from UserDefaults using NSKeyedUnarchiever in swift

I have saved an array of custom objects that conforms to NSCoding protocol in UserDefaults using NSKeyedArchiver. But when I try to retrieve it using NSKeyedUnarchiver, i get runtime errors from Xcode. I have tried initialising NSArray using unarchived data but it also failed. My guess is that while unarchiving swift doesn't understand custom elements of this array. How shall I do it?
This is how I archived array of custom objects
static func saveCategoryList(_ categoryList : [Category]!) -> Void{
let userDefaults = UserDefaults.standard
let categoryListData = NSKeyedArchiver.archivedData(withRootObject: categoryList)
userDefaults.set(categoryListData, forKey: Constants.CategoryList)
userDefaults.synchronize()
}
I get error like the attached screenshot in runtime. I am quite sure I am not doing it right. How can get my desired result?

How to Store Data Sent back using the delegate method?

I have data sent back to my VC using the delegate method. How can I possibly store it so then I can use it to send to another VC?
func DataToPass(ArrayName: [String]) { //function from delegate
Datacollect = ArrayName
print(ArrayName)
}
Here's the function used in the delegate method that holds my data. ArrayName is an array containing my data.
Datacollect is an attempt to collect it, however nothing gets stored in Datacollect.
I have already assigned Datacollect as a String array.
var Datacollect = [String]()
How can I store the data to my VC from ArrayName?
There are lot of ways of storing data and one of the simplest ways to get started is to use the built-in UserDefaults.
This is how you might use the following code inside a method to store your DataCollect array.
Let defaults = UserDefaults.standard
defaults.set(DataCollect, forKey: "DataCollect")
To retrieve the data you could use the following code inside a method:
let defaults = UserDefaults.standard
let DataCollect = defaults.array(forkey: "DataCollect")