SwiftUI: Can you use AppStorage with decoded json? - swift

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

Related

fetch all the saved binary data core data object names [duplicate]

My swift code below saves 3 images. What I want to do is overwrite iamgedata2 is func press. Imagedata2 should be replaced with Gwen. So the order should be Gwen Gwen Gwen instead of Gwen gwen2 Gwen. I don't know what really to put in func press to achieve this goal.
import UIKit; import CoreData
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .orange
let gwen = UIImage(named: "blank")
let gwen2 = UIImage(named: "g.jpg")
if let imageData = gwen.self?.pngData() {
CoredataHandler.shareInstance.saveImage(data: imageData)
}
if let imageData2 = gwen2.self?.pngData() {
CoredataHandler.shareInstance.saveImage(data: imageData2)
}
if let imageData3 = gwen.self?.pngData() {
CoredataHandler.shareInstance.saveImage(data: imageData3)
}
}
#objc func press(){
CoredataHandler.shareInstance.saveImage(data: 1)
return
}
}
class CoredataHandler : NSManagedObject {
static let shareInstance = CoredataHandler()
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
private class func getContext() -> NSManagedObjectContext {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
return appDelegate.persistentContainer.viewContext
}
func saveImage(data: Data) {
let imageInstance = Information(context: context)
imageInstance.pic = data
do {
try context.save()
} catch {
print(error.localizedDescription)
}
}
}
If you want a Core Data entity that can store more than one image, you have a few options:
Declare multiple image properties
Instead of just having a pic property, have more than one. As many as you like. Name them pic1, pic2, pic3, etc, or whatever seems best for your app. In code, read or write whichever makes sense at the time.
This is easy but not flexible, since your code can only save up to the number of attributes you declare in the model.
Use an array property with transformable
With a transformable attribute you can store any data that can be converted to a Data. In your case you'd do something like this:
Two things to notice: The type is transformable, and the custom class is [Data]. Xcode will generate code where the property type is [Data]?. You can save as many Data blobs as you want in it, representing however many images you want.
This is also easy but may use a lot of memory, because you can't access one image without loading all of them into memory at once. If you always load all of them anyway then it's no different. If you often load only one of them, this technique might use a lot more memory (e.g. 4 images would mean around 4x as much memory used).
Use a separate entity to hold the image and a to-many relationship
With this approach you'd create a new Core Data entity that only holds the image. Your existing entity would have a to-many relationship to this entity. You'd create as many image-only instances as you want, and the relationship would mean they were all available at any time.
You would probably want to make sure the to-many relationship is ordered, otherwise the images would be an unordered set that could be in any order.
This is a little more complex to write but it's flexible and doesn't have the potential memory problems of other approaches.

Can't fill my collection views with API data by using Alamofire

There is an api (https://docs.api.jikan.moe/#section/Information). I get data from it, but I can’t display them in my collection views in any way. The data should come, I checked. I implement filling the collection view cells through the view model ViewController <-> ViewModel and with Network Manager API Manager
The result is just white collectionView - Screen
For the first time I decided to work with Alamofire and apparently I don’t understand something. Please tell me what is the problem. Link to github in case someone needs it.
Updated
The problem might be with asynchronous coding. And i still have no ideas to fix it, cause don't understand the GCD as well. Screen
func fetchRequest(typeRequest: TypeRequest) -> [AnimeModel] {
var animeModels: [AnimeModel] = []
switch typeRequest {
case .name(let name):
let urlString = "https://api.jikan.moe/v4/anime?q=\(name)"
AF.request(urlString).response { response in
guard let data = response.data else { return print("NO DATA FOR - \(name)") }
do {
let json = try JSON(data: data)
let title = json["data"][0]["title_english"].string ?? "Anime"
let imageURL = json["data"][0]["images"]["jpg"]["image_url"].string ?? ""
let image = AnimeModel.downloadImage(stringURL: imageURL)
animeModels.append(AnimeModel(image: image, title: title))
print(".NAME ANIME MODELS - \(animeModels)")
} catch let error {
print(error.localizedDescription)
}
}
}
print("BEFORE RETURN ANIME MODELS - \(animeModels)")
return animeModels // returns empty array and then "animeModel.append()" is applied
}

UIKit UserDefaults in SwiftUI widget

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:).

Saving and retrieving a struct which is not codable to Userdefaults in Swift

I want to save a struct which can not be modified to UserDefaults in my code. I went through a number of solutions but everyone is advising to use Codable/NSCoding in the struct. I can not edit the struct. Does anyone have an idea how we can achieve this.
I tried to wrap the object to an NSDictionary but while trying to save, it crashes saying 'Attempt to insert non-property list object'. Is it because I have nil values in my model?
Any help is appreciated.
If you can't change your struct, then create an extension for it and conform it to Codable.
Once conformed to Codable, you can use JSONEncoder and JSONDecoder to convert to and from data and then save/retrieve from UserDefaults.
struct Sample {
var name = "Sample Struct"
}
extension Sample: Codable {
}
let obj = Sample()
let data = try? JSONEncoder().encode(obj)
//Saving in UserDefaults
UserDefaults.standard.set(data, forKey: "SampleStruct")
//Fetching from UserDefaults
if let data = UserDefaults.standard.data(forKey: "SampleStruct") {
let val = try? JSONDecoder().decode(Sample.self, from: data)
print(val) //Sample(name: "Sample Struct")
}
I tried to wrap the object to an NSDictionary
You can't save a custom model without conforming to Codable/NSCoding , if you don't need to conform then save it as a usual Array/Dictionary without wrapping any custom objects inside

How to archive data of UICollectionView elements

I'm creating an app that allows users to create sheet of notes.
In order to make this I've created a UICollectionViewController and each element is an object of the class Books.
Then I create an array that contains all this books.
My Question is: How can I memorize books, with their name, type..., in device's memory?
I try to use:
let booksDefault = UserDefaults.standard
if (booksDefault.value(forKey: "booksDefault") != nil) {
books = booksDefault.value(forKey: "booksDefault") as! [Books]
}
let booksDefault = UserDefaults.standard
booksDefault.setValue(books, forKey: "booksDefault")
booksDefault.synchronize()
but it doesn't work.
Thank you