SwiftUI - общий словарь между представлениями, неясно, какие аргументы использовать в @Main / WindowGroup

Я пытаюсь создать приложение (macOS, но то же самое для iOS), которое создает несколько сеток, результат которых должен отображаться на втором экране. Для этого я делюсь данными на этих экранах, и у меня здесь возникла проблема, я надеюсь, что кто-то может помочь или указать мне правильное направление. Я поделюсь упрощенной версией кода ниже (работает в Xcode 14.0.1)

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

Строительными блоками сетки являются ячейки

      Import Foundation
struct Cell: Comparable, Equatable, Identifiable, Hashable {
    static func == (lhs: Cell, rhs: Cell) -> Bool {
        lhs.randomVarOne == rhs.randomVarOne
    }
    var randomVarOne: Double
    var randomVarTwo: Bool
    // other vars omitted here
    var id: Int { randomVarOne } 
    static func < (lhs: Cell, rhs: Cell) -> Bool {
            return lhs.randomVarOne < rhs.randomVarOne
    }
}

здесь также есть куча функций для вычисления следующих соседних ячеек в сетке и т. д.

затем сетка определяется в классе:

      class Info: ObservableObject, Hashable {
    static func == (lhs: Info, rhs: Info) -> Bool {
        lhs.grid == rhs.grid
    }
    func hash(into hasher: inout Hasher) {
            hasher.combine(grid)
        }
    @Published var grid = [Cell]()

    var arrayTotal = 900
    @Published var toBeUsedForTheGridCalculations: Double = 0.0
    var toBeUsedToSetTheVarAbove: Double = 0.0
    var rowTotalDouble: Double {sqrt(Double(arrayTotal)) }
    var rowTotal: Int {
        Int(rowTotalDouble) != 0 ? Int(rowTotalDouble) : 10 }

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

Это структура сценария:

      struct Scenario: Comparable, Equatable, Identifiable, Hashable {
    static func == (lhs: Scenario, rhs: Scenario) -> Bool {
        lhs.scenarioNumber == rhs.scenarioNumber
    }
    func hash(into hasher: inout Hasher) {
            hasher.combine(scenarioNumber)
        }
    var scenarioNumber: Int
    var date: Date
    var thisIsOneSnapshot = [Info]()
    var id: Int { scenarioNumber }
    static func < (lhs: Scenario, rhs: Scenario) -> Bool {
        return lhs.scenarioNumber < rhs.scenarioNumber
    }
}

добавлен хешируемый, так как он использует класс Info в качестве входных данных.

Затем есть класс, показывающий обзор вывода

      class OutputOverview: ObservableObject {
    @Published var snapshot = [Scenario]()

// класс включает формулу добавления набора ячеек (сетки) и дополнительных переменных в словарь снимков. Опять же, никакой инициализатор не был необходим.

Теперь перейдем к ContentView.

      struct ContentView: View {
    @Environment(\.openURL) var openURL
    var scenarioNumberInput: Int = 0
    var timeStampAssigned: Date = Date.now

    @ObservedObject private var currentGrid: Info = Info()
    @ObservedObject private var scenarios: Combinations = Combinations()

    var usedForTheCalculations: Double = 0.0

    var rows =
    [
        GridItem(.flexible()),
     // whole list of GridItems, I do not know how to calculate these: 
     // var rows = Array(repeating: GridItem(.flexible()), count: currentGrid.rowTotal) 
     //gives error "Cannot use instance member 'currentGrid' within property initializer;
     // property iunitializers run before 'self' is available
    ]
    var body: some View {
        
        GeometryReader { geometry in
            VStack {
                ScrollView {
                    LazyHGrid(rows: rows, spacing: 0) {
                        ForEach(0..<currentGrid.grid.count, id :\.self) { w in
                            let temp = currentGrid.grid[w].varThatAffectsFontColor
                            let temp2 = currentGrid.grid[w].varThatAffectsBackground
                            Text("\(currentGrid.grid[w].randomVarOne, specifier: "%.2f")")
                                .frame(width: 25, height: 25)
                                .border(.black)
                                .font(.system(size: 7))
                                .foregroundColor(Color(wordName: temp))
                                .background(Color(wordName: temp2))
                        }
                    }
                    .padding(.top)
                }
                VStack{
                    HStack {
                        Button("Start") {
                   
                        }
                // then some buttons to do the calculations
                        Button("Add to collection"){
                            scenarios.addScenario(numbering: scenarioNumberInput, timeStamp:
                         Date.now, collection: currentGrid.grid)
                        } // this should add the newly recalculated grid to the dictionary
                        Button("Go to Results") {
                           guard let url = URL(string: "myapp://scenario") else { return }
                           openURL(url)         
                        } // to go to the screen showing the scenarios

Затем второе представление, ScenarioView:

      struct ScenarioView: View {
    @State var selectedScenario = 1
    @ObservedObject private var scenarios: OutputOverview
    
    var pickerNumbers = [ 1, 2, 3, 4 , 5] 
    // this is to be linked to the number of scenarios completed,this code is not done yet.
    var rows =
    [
        GridItem(.flexible()),
        GridItem(.flexible()),
     // similar list of GridItems here....

    var body: some View {
        Form {
            Section {
                Picker("Select a scenario", selection: $selectedScenario) { 
                  ForEach(pickerNumbers, id: \.self) {
                    Text("\($0)")
                    
                }
                }
            }
            Section {
                ScrollView {
                    if let idx = scenarios.snapshot.firstIndex(where: 
                      {$0.scenarioNumber == selectedScenario}) {
                        LazyHGrid(rows: rows, spacing: 0) {
                            
                            ForEach(0..<scenarios.snapshot[idx].thisIsOneSnapshot.count, 
                                id :\.self) { w in
                                let temp =
                         scenarios.snapshot[idx].thisIsOneSnapshot[w].varThatAffectsFontColor
                                let temp2 = 
                         scenarios.snapshot[idx].thisIsOneSnapshot[w].varThatAffectsBackground
                                Text("\(scenarios.snapshot[idx].thisIsOneSnapshot[w].randomVarOne, specifier: "%.2f")")
                                    .frame(width: 25, height: 25)
                                    .border(.black)
                                    .font(.system(size: 7))
                                    .foregroundColor(Color(wordName: temp))
                                    .background(Color(wordName: temp2))
                            }
                        }
                    }
                }
            }
        }
    }
}

Теперь, хотя приведенное выше (на данный момент) не дает мне сообщений об ошибках, я не могу запустить PreviewProvider во втором представлении. Основная проблема в @main:

      import SwiftUI

@main
struct ThisIsTheNameOfMyApp: App {
 
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .handlesExternalEvents(matching: ["main"])
        
        WindowGroup("Scenarios") {
            ScenarioView()

       // error messages here: 'ScenarioView' initializer is inaccessible due to "private"
       // protection level - I don't know what is set to private in ScenarioView that could
       // cause this
       // second error message: missing argument for parameter 'scenarios' in call
        }
        .handlesExternalEvents(matching: ["scenario"])
    }
}

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

Я попытался добавить предварительные данные в @main следующим образом.

      
@main
struct FloodModelScenarioViewerApp: App {
    @State var scenarios = Scenario(scenarioNumber: 1, date: Date.now)
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .handlesExternalEvents(matching: ["main"])
        
        WindowGroup("Scenarios") {
            ScenarioView(scenarios: scenarios)

        }
        .handlesExternalEvents(matching: ["scenario"])
    }
}

Это по-прежнему дает 2 сообщения об ошибках:

  • та же проблема в отношении того, что инициализатор ScenarioView недоступен из-за того, что он «частный»
  • Не удается преобразовать значение типа «Сценарий» в ожидаемый тип аргумента «OutputOverview».

2 ответа

Просто удалитеprivateот

      @ObservedObject private var scenarios: OutputOverview

Значение исходит от родителя, поэтому родителю нужен доступ. Так положи

      @StateObject private var scenarios: OutputOverview = .init()

вFloodModelScenarioViewerApp

@StateObjectдля инициализацииObservableObjectпесок@ObservedObjectдля передачи их вокруг.

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

  1. ВCell, вы не должны использоватьidэто переменная, это может привести к непоследовательному поведению. Используйте что-то вроде:
      let id = UUID()
  1. Когда вы инициализируетеContentView, вы не можете использовать внутри переменной, потому что она будет недоступна до инициализации всех переменных. Это означает, что вы пытаетесь инициализироватьrowsдоcurrentGridна самом деле существует. Вы можете попробовать использовать.onAppearмодификатор:
          var rows = [GridItem]()

    var body: some View {
        GeometryReader { geometry in
            // ... view code in here
        }
        .onAppear {
            var rows = Array(repeating: GridItem(.flexible()), count: currentGrid.rowTotal)
        }
    }

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

  1. Инициализатор сообщения «ScenarioView» недоступен из-за «частного» уровня защиты кажется очевидным: вы должны указать значение для переменнойscenarios(у него нет значения по умолчанию), но он помечен как . Удалятьprivate.
      @ObservedObject var scenarios: OutputOverview

Затем не забудьте передать значение типаOutputOverviewдля переменной при вызове представления:

      ScenarioView(scenarios: aVariableOfTypeOutputOverview) 
  1. Ошибка несоответствия типа, которую вы получаете внутри@mainкод тоже понятен - вы определили переменную типаScenario:
      @State var scenarios = Scenario(scenarioNumber: 1, date: Date.now)

ноScenarioViewтребуется другой тип:

      @ObservedObject private var scenarios: OutputOverview

Один из них нуждается в изменении, чтобы ваш код работал.

Другие вопросы по тегам