如何使用@StateObject 修改布尔值?

Posted

技术标签:

【中文标题】如何使用@StateObject 修改布尔值?【英文标题】:How to modify Boolean value using @StateObject? 【发布时间】:2020-07-02 15:31:06 【问题描述】:

我试图使用@StateObject 重新创建地标应用程序的功能,Apple 去年以tutorial 发布了该应用程序。我有以下代码。这是一个基本的应用程序,您可以看到 JSON 对象的 isFavorite 值是真还是假。在 Apple 的示例中,可以通过点击按钮更改 isFavorite 值,然后保持更改。

不过,我并不完全理解 Apple 的示例。如何在不重写 JSON 本身的情况下保留值?这不是一个坏例子吗?

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)")
        
    

【问题讨论】:

【参考方案1】:

您似乎正在创建 两个 UserData 的实例。

struct ContentView: View 
    ...
    @StateObject var userData = UserData()
    ...


struct ContentDetail: View 
    ...
    @StateObject var userData = UserData()
    ...

使用@StateObject 并不意味着它的实例对于您的所有视图都是相同的。当 SwiftUI 决定使您的视图无效/重绘时,@StateObject 将持续存在,但这并不意味着同一对象将在全局范围内可用。

ContentView 的示例中,您正在访问(并进行更改)UserData 的一个实例,而在ContentDetail 中,您正在使用另一个 实例。这也意味着您不必要地对 JSON 进行两次解码。

如果您希望UserData 在您当前的环境中全球可用,您可以尝试@EnvironmentObject

您也可以将UserData 传递给您的DetailView

NavigationLink(destination: ContentDetail(itemID: item.id - 1, userData: userData))
...

struct ContentDetail: View 
    ...
    @ObservedObject var userData: UserData // <- declare only
    ...

请注意,您的详细视图中不需要@StateObject。您可以安全地使用@ObservedObject,因为它已传递到您的详细视图(而不是在其中创建)。详情请见this question。

【讨论】:

以上是关于如何使用@StateObject 修改布尔值?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 StateObject 并与 JSON loader 结合?

如何以 stateobject 作为参数初始化视图?

SwiftUI:如何在父视图中初始化新的 StateObject?

如何使用 axios 返回布尔值

如何在意图之间传递布尔值

如何在 Swift 中使用计时器切换布尔值