"xx" 是 get-only 属性 - 如何将解码的 Json 数据分配给 class(嵌套视图模型)Swift

"xx" is get-only property - How to Assign decoded Json Data to class (nested view model) Swift

我有一个 watchapp,它通过 json/file 传输从 iPhone 接收父视图模型(嵌套视图模型)数据。我正在使用@KaneCheshire 的“Communicator”库。

数据流为

  1. Watch App 启动 - 将空的父视图模型 (ReceivedDayProgViewModel) 加载到应用中并在 WatchLoadingView 中显示 activity 指示器。
  2. WatchLoadingView 设置观察者从 iPhone
  3. 接收实际的 json data/file
  4. WatchLoadingView 的观察者接收实际的 json 数据,将它们解码为 ParentViewModel 对象 (ReceivedDayProgViewModel)

在第 3 步之后,我很难将解码后的对象实际分配给最初为空的父视图模型 (ReceivedDayProgViewModel)。有一条错误消息说“**Cannot assign to property: 'receivedDayProgramVM' is a get-only property**

所以我确实查找了错误并且知道我的父视图模型实际上是一个“计算的-属性”而不是存储的属性 - 但我仍在努力添加代码以确保“set/write”函数嵌入到父视图模型中(ReceivedDayProgViewModel)。

非常感谢 guidance/advice 如何更改我的代码以允许在父视图模型 (ReceivedDayProgViewModel) 中执行“set/write”操作,以便可以完成以下步骤。

  1. 将解码对象 (let ReceivedDayProgViewModel = try! decoder.decode(ReceivedDayProgViewModel.self, from: ReceivedDayProgVMData))(values/data 来自 iPhone)分配到 ContentView 中的 @StateObject (receivedDayProgramVM)

父视图模型

import Foundation
import Combine

class ReceivedDayProgViewModel: ObservableObject, Codable {
    
    @Published var workoutModel = WorkoutModel()
    @Published var watchDayProgramSequence: Int = 0
    @Published var exerciseVM: [WatchExerciseViewModel] = []
    
    enum CodingKeys: String, CodingKey {
        
        case dayProgramSeq
        case exerciseVM
        
    }
    
    init() {
           //initiliazed empty view model via this function
    }
    
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        
        watchDayProgramSequence = try container.decode(Int.self, forKey: .dayProgramSeq)
        exerciseVM = try container.decode([WatchExerciseViewModel].self, forKey: .exerciseVM)
        
        do {
            watchDayProgramSequence = try container.decode(Int.self, forKey: .dayProgramSeq)
        } catch DecodingError.typeMismatch {
            do {
                watchDayProgramSequence = try Int(container.decode(String.self, forKey: .dayProgramSeq)) ?? 0
            }
        }
        
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        
        try container.encode(watchDayProgramSequence, forKey: .dayProgramSeq)
        try container.encode(exerciseVM, forKey: .exerciseVM)

    }//encode function ends
    
}

WatchApp

import SwiftUI
import Communicator

@main
struct WatchApp: App {

    @SceneBuilder var body: some Scene {
        WindowGroup {
            NavigationView {
                ContentView(selectedTab: 0, receivedDayProgramVM: ReceivedDayProgViewModel())
            }
        }

        WKNotificationScene(controller: NotificationController.self, category: "myCategory")
    }
}

内容视图

import SwiftUI
import WatchKit
import Combine
import Communicator

struct ContentView: View {
    @State var selectedTab = 0
    
    @StateObject var receivedDayProgramVM = ReceivedDayProgViewModel()
    
    var body: some View {
        
        NavigationView {
            
            //Show WatchLoadingView, if dayprogramVM is being fetched
            if receivedDayProgramVM.exerciseVM.count == 0 {
                
                   WatchLoadingView()
                    .onAppear {
                        //Add Communicator functions if required
                         Blob.observe { Blob in

                             if Blob.identifier == "dayProgramData" {

                                 let ReceivedDayProgVMData = Blob.content
                                 print("WatchConnectivity blob setData printing \(ReceivedDayProgVMData)")

                                 let decoder = JSONDecoder()
                                 
                                 let ReceivedDayProgViewModel = try! decoder.decode(ReceivedDayProgViewModel.self, from: ReceivedDayProgVMData)
                                 
                                 //Assign decoded class(parent view model) to environment object in contentView
//This is the line that causes error showing receivedDayProgramVM is get-only property
                                 receivedDayProgramVM = ReceivedDayProgViewModel

                                 
                             } //Blob for "dayProgramData" code ends
                             
                             
                         } //Blob observation code ends
                    } else {
                
                TabView(selection: $selectedTab) {
                    
                    WatchControlView().id(0)
                    SetInformationView().id(1)
                    SetRestDetailView().id(2)
                    //Check if WatchControlView can be vertically scrollable
                    NowPlayingView().id(3)
                }
                .environmentObject(receivedDayProgramVM)
            }

        }
    }
}
            

WatchLoadingView

import SwiftUI
import Communicator

struct WatchLoadingView: View {
    
    var body: some View {
        
        GeometryReader { geometry in
            
            let rect = geometry.frame(in: .global)
         
            VStack {
                ProgressView()
                Text("Fetching Data...")
                    .font(.body)
            }
        }
    }
}

在从 Joakim 和 Ptit 的评论中获得正确线索后,我发布了我的答案。 根据他们的建议,我已经为手表应用程序分离了(数据)模型和视图模型

有点 time-consuming,但我设法更新了应用程序设计,它使我能够将传输的数据存储到适当的视图模型中 class。

请参阅下面的代码摘录以供将来参考。

WatchDayProgram 结构(数据模型)

import Foundation
import Combine

struct WatchDayProgram: Codable {
    
    var watchDayProgramSequence: Int = 0
    var exerciseVM: [WatchExercise]
    
    enum CodingKeys: String, CodingKey {
        
        case dayProgramSeq
        case exerciseVM
        
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        
        watchDayProgramSequence = try container.decode(Int.self, forKey: .dayProgramSeq)
        exerciseVM = try container.decode([WatchExercise].self, forKey: .exerciseVM)
        
        do {
            watchDayProgramSequence = try container.decode(Int.self, forKey: .dayProgramSeq)
        } catch DecodingError.typeMismatch {
            do {
                watchDayProgramSequence = try Int(container.decode(String.self, forKey: .dayProgramSeq)) ?? 0
            }
        }

    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        
        try container.encode(watchDayProgramSequence, forKey: .dayProgramSeq)
        try container.encode(exerciseVM, forKey: .exerciseVM)

    }//encode function ends
    
}

WatchDayProgViewModel

import Foundation
import Combine

class ReceivedDayProgViewModel: ObservableObject {

    @Published var workoutModel = WorkoutModel()
    @Published var watchDayProgramSequence: Int = 0
    @Published var exerciseVM: [WatchExerciseViewModel] = []
    
    
    init() {
           //initiliazed empty view model via this function, so empty view model class can be loaded onto Watch app when it starts while awaiting for the file transfer to be complete
    }
    
}

ContentView

import SwiftUI
import WatchKit
import Combine
import Communicator

struct ContentView: View {
    @State var selectedTab = 0
    
    @StateObject var receivedDayProgramVM = ReceivedDayProgViewModel()
    
    var body: some View {
        
        NavigationView {
            
            //Show WatchLoadingView, if dayprogramVM is being fetched
            if receivedDayProgramVM.exerciseVM.isEmpty == true {
                
                   WatchLoadingView()
                    .onAppear {

                         Blob.observe { Blob in

                             if Blob.identifier == "dayProgramData" {

                                 let ReceivedDayProgVMData = Blob.content
                                 print("WatchConnectivity blob setData printing \(ReceivedDayProgVMData)")

                                 let decoder = JSONDecoder()
                                 let ReceivedDayProgViewModel = try! decoder.decode(WatchDayProgram.self, from: ReceivedDayProgVMData)
                                 
                                 
                                 for (i, exercise) in ReceivedDayProgViewModel.exerciseVM.enumerated() {
                                     
                                     DispatchQueue.main.async {
                                         receivedDayProgramVM.exerciseVM.append(WatchExerciseViewModel(exercise: exercise))
                                     }

                                 
                             } //Blob for "dayProgramData" code ends
                             
                             
                         } //Blob observation code ends
                    } else {
                
                TabView(selection: $selectedTab) {
                    
                    WatchControlView().id(0)
                    SetInformationView().id(1)
                    SetRestDetailView().id(2)
                    //Check if WatchControlView can be vertically scrollable
                    NowPlayingView().id(3)
                }
                .environmentObject(receivedDayProgramVM)
            }

        }
    }
}