BLACK FRIDAY: Save 50% on all books and bundles! >>

SOLVED: Modifying Boolean value with @StateObject

Forums > SwiftUI

I was trying to use @StateObject to recreate the functionality of Landmarks app, which Apple released as tutorial last year. I have the following code. It's a basic app that you can see JSON objects' isFavorite value true or false. In Apple's example it's possible to change the isFavorite value by tapping on a button, and then persist the change.

Still, I do not fully understand Apple's example here. How it is possible to persist the value without rewriting the JSON itself? Isn't it a bad example?

import SwiftUI
import Combine
import Foundation

struct Item: Codable, Identifiable, Equatable {
    var id: Int
    var name: String
    var isFavorite: Bool
}

final class UserData: ObservableObject {
    @Published var items = Bundle.main.decode([Item].self, from: "data.json")
    @Published var showFavorites = false
}

struct ContentView: View {
    @State var itemID = Item.ID()
    @StateObject var userData = UserData()

    var body: some View {
        NavigationView {
            VStack {
                Toggle(isOn: $userData.showFavorites) {
                    Text("Show Favorites Only")
                }
                List {
                    ForEach(userData.items) { item in
                        if !userData.showFavorites || item.isFavorite {
                            NavigationLink(destination: ContentDetail(itemID: item.id - 1)) {
                                ContentRow(item: item)
                            }
                        }
                    }
                }
            }
        }
    }
}

struct ContentRow: View {
    var item: Item

    var body: some View {
        HStack {
            Text(item.name)
            Spacer()
            if item.isFavorite {
                Image(systemName: "star.fill")
                    .imageScale(.medium)
                    .foregroundColor(.yellow)
            }
        }
    }
}

struct ContentDetail: View {
    @State var itemID = Item.ID()
    @StateObject var userData = UserData()

    var body: some View {
        VStack {
            Button {
                userData.items[itemID].isFavorite.toggle()
            } label: {
                if userData.items[itemID].isFavorite {
                    Image(systemName: "star.fill")
                        .foregroundColor(Color.yellow)
                } else {
                    Image(systemName: "star")
                        .foregroundColor(Color.gray)
                }
            }
            Text(userData.items[itemID].name)
        }
    }
}

extension Bundle {
    func decode<T: Decodable>(_ type: T.Type, from file: String, dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .deferredToDate, keyDecodingStrategy: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys) -> T {
        guard let url = self.url(forResource: file, withExtension: nil) else {
            fatalError("Failed to locate \(file) in bundle.")
        }

        guard let data = try? Data(contentsOf: url) else {
            fatalError("Failed to load \(file) from bundle.")
        }

        let decoder = JSONDecoder()
        decoder.dateDecodingStrategy = dateDecodingStrategy
        decoder.keyDecodingStrategy = keyDecodingStrategy

        do {
            return try decoder.decode(T.self, from: data)
        } catch DecodingError.keyNotFound(let key, let context) {
            fatalError("Failed to decode \(file) from bundle due to missing key '\(key.stringValue)' not found – \(context.debugDescription)")
        } catch DecodingError.typeMismatch(_, let context) {
            fatalError("Failed to decode \(file) from bundle due to type mismatch – \(context.debugDescription)")
        } catch DecodingError.valueNotFound(let type, let context) {
            fatalError("Failed to decode \(file) from bundle due to missing \(type) value – \(context.debugDescription)")
        } catch DecodingError.dataCorrupted(_) {
            fatalError("Failed to decode \(file) from bundle because it appears to be invalid JSON")
        } catch {
            fatalError("Failed to decode \(file) from bundle: \(error.localizedDescription)")
        }
    }
}

   

It looks like I've been creating a second instance of UserData() all along.

   

Save 50% in my Black Friday sale.

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

Not logged in

Log in
 

Link copied to your pasteboard.