Наблюдаемый объект SwiftUI не обновляется

#swiftui

Вопрос:

На самом деле я только начинаю работать со SwiftUI и пытаюсь разобраться в MVVM.

Приведенный ниже код отображает высоту и 4 кнопки переключения, до сих пор я подключил только 2.

Майор вверх и майор вниз.

При нажатии я вижу в консоли, что значение изменяется, как и ожидалось.

Чего я не вижу, так это обновления основного дисплея, отражающего изменения.

Я попытался провести рефакторинг своего кода, чтобы включить модель представления в каждую структуру, но все еще не вижу изменений.

Я думаю, что я рассмотрел основы, но я в тупике, сейчас я использую один файл, но планирую переместить модель и модель представления в отдельные файлы, когда у меня будет рабочий макет.

Спасибо, что посмотрели.

 import SwiftUI

/// This is our "ViewModel"
class setHeightViewModel: ObservableObject {
    struct ImperialAndMetric {
        var feet = 16
        var inches = 3
        var meters = 4
        var CM = 95
        var isMetric = true
    }
    
    // The model should be Private?
    // ToDo: Fix the private issue.
    @Published var model = ImperialAndMetric()
        
    // Our getters for the model
    var feet: Int { return model.feet }
    
    var inches: Int { return model.inches }
    
    var meters: Int { return model.meters }
    
    var cm: Int { return model.CM }
    
    var isMetric: Bool { return model.isMetric }
    
    /// Depending upon the selected mode, move the major unit up by one.
    func majorUp() {
        if isMetric == true {
            model.meters  = 1
            print("Meters is now: (meters)")
        } else {
            model.feet  = 1
            print("Feet is now: (feet)")
        }
    }
    
    /// Depending upon the selected mode, move the major unit down by one.
    func majorDown() {
        if isMetric == true {
            model.meters -= 1
            print("Meters is now: (meters)")
        } else {
            model.feet -= 1
            print("Feet is now: (feet)")
        }
    }
    
    /// Toggle the state of the display mode.
    func toggleMode() {
        model.isMetric = !isMetric
    }
}

// This is our View

struct ViewSetHeight: View {
    
    // UI will watch for changes for setHeihtVM now.
    @ObservedObject var setHeightVM = setHeightViewModel()
    
    var body: some View {
        NavigationView {
            Form {
                ModeArea(viewModel: setHeightVM)
                SelectionUp(viewModel: setHeightVM)
                
                // Show the correct height format
                if self.setHeightVM.isMetric == true {
                    ValueRowMetric(viewModel: self.setHeightVM)
                } else {
                    ValueRowImperial(viewModel: self.setHeightVM)
                }
                
                SelectionDown(viewModel: setHeightVM)
                
            }.navigationTitle("Set the height")
        }
        
    }
}

    struct ModeArea: View {
        var viewModel: setHeightViewModel
        
        var body: some View {
            Section {
                if viewModel.isMetric == true {
                    SwitchImperial(viewModel: viewModel)
                } else {
                    SwitchMetric(viewModel: viewModel)
                }
            }
        }
    }
    
    struct SwitchImperial: View {
        var viewModel: setHeightViewModel
        
        var body: some View {
            HStack {
                Button(action: {
                    print("Imperial Tapped")
                }, label: {
                    Text("Imperial").onTapGesture {
                        viewModel.toggleMode()
                    }
            })
                Spacer()
                Text("(viewModel.feet)'-(viewModel.inches)"").foregroundColor(.gray)
            }
        }
    }
        
    struct SwitchMetric: View {
        
        var viewModel: setHeightViewModel
        
        var body: some View {
            HStack {
                Button(action: {
                    print("Metric Tapped")
                }, label: {
                Text("Metric").onTapGesture {
                    viewModel.toggleMode()
                   }
            })
                Spacer()
                Text("(viewModel.meters).(viewModel.cm) m").foregroundColor(.gray)
            }
        }
    }

    struct SelectionUp: View {
        
        var viewModel: setHeightViewModel
        
        var body: some View {
            Section {
                HStack {
                        Button(action: {
                            print("Major Up Tapped")
                            viewModel.majorUp()
                        }, label: {
                            Image(systemName: "chevron.up").padding()
                        })
                    
                        Spacer()
                    
                    Button(action: {
                        print("Minor Up Tapped")
                    }, label: {
                        Image(systemName: "chevron.up").padding()
                    })
                }
            }
        }
    }

    struct ValueRowImperial: View {
        
        var viewModel: setHeightViewModel
        
        var body: some View {
            HStack {
                Spacer()
                Text(String(viewModel.feet)).accessibility(label: Text("Feet"))
                Text("'").foregroundColor(Color.gray).padding(.horizontal, -10.0).padding(.top, -15.0)
                Text("-").foregroundColor(Color.gray).padding(.horizontal, -10.0)
                Text(String(viewModel.inches)).accessibility(label: Text("Inches"))
                Text(""").foregroundColor(Color.gray).padding(.horizontal, -10.0).padding(.top, -15.0)
                Spacer()
            }.font(.largeTitle).padding(.zero)
        }
    }

    struct ValueRowMetric: View {
        
        var viewModel: setHeightViewModel
        
        
        var body: some View {
            HStack {
                Spacer()
                Text(String(viewModel.meters)).accessibility(label: Text("Meter"))
                Text(".").padding(.horizontal, -5.0).padding(.top, -15.0)
                Text(String(viewModel.cm)).accessibility(label: Text("CM"))
                Text("m").padding(.horizontal, -5.0).padding(.top, -15.0).font(.body)
                Spacer()
            }.font(.largeTitle)
        }
    }

    struct SelectionDown: View {
        
        var viewModel: setHeightViewModel
        
        var body: some View {
            Section {
                HStack {
                        Button(action: {
                            print("Major Down Tapped")
                            viewModel.majorDown()
                        }, label: {
                            Image(systemName: "chevron.down").padding()
                        })
                    
                        Spacer()
                    
                    Button(action: {
                        print("Minor Down Tapped")
                    }, label: {
                        Image(systemName: "chevron.down").padding()
                    })
                }
            }
        }
    }
 

Комментарии:

1. Так много кода… model это опубликованное свойство, так что это то, что вы должны использовать в своем представлении, а не вычисляемые свойства

2. Спасибо за ваш ответ, если я попытаюсь получить доступ к модели напрямую в valueRowMetric, это не входит в область применения. Вот почему я передаю модель представления.

3. @JoakimDanielson: Но для вычисляемых свойств нет проблем, они просто будут работать как обычно

4. Я не имел в виду, что вы должны получить к нему прямой доступ. Я имел в виду, что вы должны использовать его при доступе к свойствам, например viewModel.model.feet , но опять же, как уже упоминалось, это может быть не проблемой.

Ответ №1:

В тот момент, когда вы получаете setHeightViewModel в различных представлениях как var, вы должны получить его как an ObservedObject .

Я предлагаю вам попробовать это, в ViewSetHeight

 @StateObject var setHeightVM = setHeightViewModel()  
 

и во всех других ваших представлениях, где вы передаете эту модель, используйте:

 @ObservedObject var viewModel: setHeightViewModel
 

например, в ModeArea , SwitchImperial , SwitchMetric SelectionUp , и т. Д…

В качестве альтернативы вы могли бы использовать @EnvironmentObject … чтобы передать setHeightViewModel всем взглядам, которые в этом нуждаются.

Комментарии:

1. Круто, что это работает, я думаю, мне удалось запутаться, я хотел попытаться сделать структуры как можно меньше, разбив код на более мелкие фрагменты, но, похоже, не смог сделать наблюдаемый объект доступным так далеко в дереве представлений.