"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”库。
数据流为
- Watch App 启动 - 将空的父视图模型 (
ReceivedDayProgViewModel
) 加载到应用中并在 WatchLoadingView
中显示 activity 指示器。
WatchLoadingView
设置观察者从 iPhone 接收实际的 json data/file
WatchLoadingView
的观察者接收实际的 json 数据,将它们解码为 ParentViewModel 对象 (ReceivedDayProgViewModel
)
在第 3 步之后,我很难将解码后的对象实际分配给最初为空的父视图模型 (ReceivedDayProgViewModel
)。有一条错误消息说“**Cannot assign to property: 'receivedDayProgramVM' is a get-only property**
”
所以我确实查找了错误并且知道我的父视图模型实际上是一个“计算的-属性”而不是存储的属性 - 但我仍在努力添加代码以确保“set/write”函数嵌入到父视图模型中(ReceivedDayProgViewModel
)。
非常感谢 guidance/advice 如何更改我的代码以允许在父视图模型 (ReceivedDayProgViewModel
) 中执行“set/write”操作,以便可以完成以下步骤。
- 将解码对象 (
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)
}
}
}
}
我有一个 watchapp,它通过 json/file 传输从 iPhone 接收父视图模型(嵌套视图模型)数据。我正在使用@KaneCheshire 的“Communicator”库。
数据流为
- Watch App 启动 - 将空的父视图模型 (
ReceivedDayProgViewModel
) 加载到应用中并在WatchLoadingView
中显示 activity 指示器。 WatchLoadingView
设置观察者从 iPhone 接收实际的 json data/file
WatchLoadingView
的观察者接收实际的 json 数据,将它们解码为 ParentViewModel 对象 (ReceivedDayProgViewModel
)
在第 3 步之后,我很难将解码后的对象实际分配给最初为空的父视图模型 (ReceivedDayProgViewModel
)。有一条错误消息说“**Cannot assign to property: 'receivedDayProgramVM' is a get-only property**
”
所以我确实查找了错误并且知道我的父视图模型实际上是一个“计算的-属性”而不是存储的属性 - ReceivedDayProgViewModel
)。
非常感谢 guidance/advice 如何更改我的代码以允许在父视图模型 (ReceivedDayProgViewModel
) 中执行“set/write”操作,以便可以完成以下步骤。
- 将解码对象 (
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)
}
}
}
}