Значение выбранной опции из средства выбора SwiftUI не обновляет представление

#ios #swift #swiftui #observedobject

#iOS #swift #swiftui #наблюдаемый объект

Вопрос:

В приложении SwiftUI у меня есть следующее. В принципе, у меня есть некоторые настройки (класс настроек), которые я хотел бы использовать во всем приложении. У меня есть представление настроек, в котором отображается средство выбора для выбора значения одного из параметров. И другие представления приложения будут использовать только текущее установленное значение настроек. Следующая настройка работает в том смысле, что в contentView я вижу правильное значение firstLevel параметра. Но проблема в том, что в SettingsView, я думаю, поскольку selectedFirstLevel это не @State, его правильное значение не отображается в средстве выбора, которое я выбираю, чтобы выбрать либо even или odd (как ни странно, в первый раз это правильно). Этот выбор правильно переносится в contentView, но он неправильно отображается в SettingsView. Как я могу исправить эту проблему?

Настройки.swift

 import Foundation

class Settings: ObservableObject {
    static let shared: Settings = Settings()
    @Published var firstLevel: FirstLevel = .even
}

enum FirstLevel: String, CaseIterable, Identifiable {
    case even
    case odd
    var id: String { self.rawValue }
}
  

contentView.swift

 import SwiftUI

struct ContentView: View {
    @State private var showSettings: Bool = false
    @ObservedObject var settings = Settings.shared
    var body: some View {
        VStack {
            SettingsButton(showSettings: $showSettings, settings: settings)
            Text(settings.firstLevel.id)
                .padding()
        }
        
    }
}

struct SettingsButton: View {
    @Binding var showSettings: Bool
    var settings: Settings
    var firstLevel: Binding<FirstLevel> {
        return Binding<FirstLevel>(
            get: {
                return self.settings.firstLevel
        }) { newFirstLevel in
            self.settings.firstLevel = newFirstLevel
        }
    }
    var body: some View {
        Button(action: { self.showSettings = true }) {
            Image(systemName: "gear").imageScale(.large)
        }
        .sheet(isPresented: $showSettings) {
            SettingsView(selectedFirstLevel: self.firstLevel)
        }
    }
}
  

SettingsView.swift

 import SwiftUI

struct SettingsView: View {

    @Binding var selectedFirstLevel: FirstLevel
    var body: some View {
        NavigationView {
            Form {
                Picker("First Level", selection: $selectedFirstLevel) {
                    ForEach(FirstLevel.allCases) { level in
                        Text(level.rawValue).tag(level)
                    }
                }
            }
            .navigationBarTitle("Settings", displayMode: .inline)
        }
    }
}
  

Ответ №1:

Это выглядит слишком сложным, более того, привязка ненадежна как связь между различными иерархиями представлений (в вашем случае это лист).

Вот упрощенный и проработанный вариант. Протестировано с Xcode 12 / iOS 14.

 struct ContentView: View {
    @ObservedObject var settings = FLevelSettings.shared
    var body: some View {
        VStack {
            SettingsButton(settings: settings)
            Text(settings.firstLevel.id)
                .padding()
        }

    }
}

struct SettingsButton: View {
    @State private var showSettings: Bool = false
    var settings: FLevelSettings
    var body: some View {
        Button(action: { self.showSettings = true }) {
            Image(systemName: "gear").imageScale(.large)
        }
        .sheet(isPresented: $showSettings) {
            FLevelSettingsView(settings: self.settings)
        }
    }
}
struct FLevelSettingsView: View {

    @ObservedObject var settings: FLevelSettings
    var body: some View {
        NavigationView {
            Form {
                Picker("First Level", selection: $settings.firstLevel) {
                    ForEach(FirstLevel.allCases) { level in
                        Text(level.rawValue).tag(level)
                    }
                }
            }
            .navigationBarTitle("Settings", displayMode: .inline)
        }
    }
}
  

Примечание: это может быть еще более упрощено, если вы хотите, из-за наличия FLevelSettings.shared , поэтому вы можете использовать его FLevelSettingsView непосредственно внутри. На всякий случай.