2

I want to set the variable "isfavorite" mapped to "id" as UserDefaults. I am able to save the whole of the struct encoded as JSON to UserDefaults. And then decode the UserDefaults JSON on init. There are two problems with this approach

1) I am using UserDefault to store a JSON file that could get bigger over time which is not the ideal usecase for UserDefaults

2) I am not able to update the content as during initialization, the last saved JSON on UserDefaults is loading

struct Labvalueslist: Codable, Identifiable, Equatable {
    var topic: String
    var sirange: String
    var usrange: String
    var id: Int
    var isfavorite: Bool }

var labvaluesdata: [Labvalueslist] = load("labvalues.json")

final class UserData: ObservableObject {
    @Published var labvaluesUserdata = labvaluesdata
        {
        didSet {
            let encoder = JSONEncoder()
            if let encoded = try?
                encoder.encode(labvaluesUserdata) {
                UserDefaults.standard.set(encoded, forKey: "isfavorite")
            }
        }
    }
    init () {
        if let labvaluesUserdata = UserDefaults.standard.data(forKey: "labvaluesUserdata")
        {
            let decoder = JSONDecoder()
            if let decoded = try?
                decoder.decode([Labvalueslist].self, from: labvaluesUserdata) {
                self.labvaluesUserdata = decoded
                return
            }
        }
        self.labvaluesUserdata = labvaluesdata
    }
}

struct ImageView: View {
    var list: Labvalueslist

    @ObservedObject var userData = UserData()

    var anatomyfavIndex: Int {
        userData.labvaluesUserdata.firstIndex(where: { $0.id == list.id})!
    }
// This is where I am confused; how do I get the "isfavorite" mapped to "id" and store it as UserDefaults and use only those values on init 

var body: some View {

            VStack {
        ScrollView {
            VStack(alignment: .leading)
            {
            Text("Normal Range:").font(.headline)
            HStack
                { Text("SI: " + list.sirange)
                Spacer()
                Text("US: " + list.usrange)
                }
            .padding(EdgeInsets(top: 10, leading: 0, bottom: 10, trailing: 0))
            }
        }.foregroundColor(.black)
            }.navigationBarTitle(Text(list.topic), displayMode: .inline)
           .padding(EdgeInsets(top: 10, leading: 10, bottom: 0, trailing: 5))
                .navigationBarItems(trailing: Button(action: {
                    self.userData.labvaluesUserdata[self.anatomyfavIndex].isfavorite.toggle()
                }) {
                    if self.userData.labvaluesUserdata[self.anatomyfavIndex].isfavorite {
                        Image(systemName: "star.fill").foregroundColor(.yellow).scaleEffect(1.5).padding (.trailing, 20)
                    }
                    else {
                        Image(systemName: "star")
                            .foregroundColor(.yellow)
                            .scaleEffect(1.5)
                            .padding (.trailing, 20)
                }
            })

}
}
Dar
  • 57
  • 1
  • 11

3 Answers3

0

1.) what about saving it to a file? 2.) why don't you just save the favorite id? (if it is just one, i am not sure) like this: key: "myFavoriteID" - id

Chris
  • 7,579
  • 3
  • 18
  • 38
  • "favorite" can be multiple. That is why I am using the toggle self.userData.labvaluesUserdata[self.anatomyfavIndex].isfavorite.toggle() for each topic represented by a id – Dar Nov 27 '19 at 10:25
  • 1
    ok, then why don't you just save an array of ids which are your favorites? – Chris Nov 27 '19 at 11:20
  • Yeah. That’s a great idea. Will try. Would you be able to guide me on how to create it. P.S: I am not from software background. Doctor trying to code. – Dar Nov 27 '19 at 11:40
  • begin with: favoriteIDs : [Int] = [] . -> https://stackoverflow.com/questions/37599632/swift-read-write-array-of-arrays-to-from-file . in that link you can see how to save/read that array to a file. – Chris Nov 27 '19 at 12:39
  • How do I pull "anatomyfavIndex" id when the isfavorite toggle is on ? – Dar Nov 27 '19 at 13:53
0

You can use map to get an [Bool] array from you array of structs.

@State var isfavoriteArray : [Bool] = labvaluesdata.map{$0.isfavorite}

If you want to get isfavorite tied to id - you can get array of dictionaries [id: isfavorite] like this

@State var idIsFaveDict: [Int: Bool] = Dictionary(uniqueKeysWithValues: labvaluesdata.map { ($0.id, $0.isfavorite) })

did this help?

Nalov
  • 578
  • 5
  • 9
  • Nope. How would I store it as UserDefaults and load it on init... – Dar Nov 27 '19 at 14:30
  • UserDefaults.standard.set(isfavoriteArray, forKey: "isfavoriteArray") What do you mean load it on init? You are already loading userdefaults on init. Do you mean you want to get the array when the app launches? in that case add .onAppear{isfavoriteArray = Userrdefaults.Standart...} to you ScrollView. – Nalov Nov 27 '19 at 16:28
  • The problem with the present init is it is loading the entire JSON file; which makes it impossible to update any content. Everytime, the JSON in the userdefault gets loaded. – Dar Nov 27 '19 at 16:53
  • Have you considered to remove it from init and doing it .onAppear{} one time? . save the changes every time they are happening, but load only in .onAppear{}. – Nalov Nov 27 '19 at 17:10
  • I was able to do things as mentioned by you. But I get this error when I try to save UserDefaults "Attempt to insert non-property list object" – Dar Dec 06 '19 at 07:01
  • Can you show your code? I can take a look at project. – Nalov Dec 06 '19 at 22:10
0

Here's a sample app which should do what something like what you're looking for. Instead of saving it as one entry in user defaults, I'm saving it as multiple. I think they key is writing the ObservableObject correctly.

struct ContentView: View {
    var body: some View {
        VStack {
            ButtonView(key: "favorite:1")
            ButtonView(key: "favorite:ab12")
            ButtonView(key: "favorite:2jjf")
        }
    }
}

struct ButtonView: View {
    @ObservedObject private var userDefaultsKey: UserDefaultsKey

    init(key: String) {
        userDefaultsKey = UserDefaultsKey(key: key)
    }

    var body: some View {
        Button(action: buttonTapped) {
            Text(userDefaultsKey.key + ": " + (userDefaultsKey.value ? "liked" : "not liked"))
        }
    }

    func buttonTapped() {
        UserDefaults.standard.set(!userDefaultsKey.value, forKey: userDefaultsKey.key)
    }
}

class UserDefaultsKey: ObservableObject {
    let key: String

    @Published var value = false

    init(key: String) {
        self.key = key

        value = UserDefaults.standard.bool(forKey: key)

        NotificationCenter.default.addObserver(forName: UserDefaults.didChangeNotification, object: nil, queue: nil) { [weak self] _ in
            guard let self = self else { return }
            self.value = UserDefaults.standard.bool(forKey: self.key)
        }
    }
}

Note that in the simulator, user defaults aren't necessarily saved unless you actually quit the app in the simulator (if you run again from Xcode, user defaults sometimes aren't saved: Xcode 9 simulator doesn't save userdefaults)

plivesey
  • 2,337
  • 1
  • 16
  • 18