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
для передачи их вокруг.
Я не знаю, будет ли ваш код работать после того, как вы прочитаете этот вопрос, и это потому, что есть много вещей, которые нужно исправить, но вы можете начать с этого:
- В
Cell
, вы не должны использоватьid
это переменная, это может привести к непоследовательному поведению. Используйте что-то вроде:
let id = UUID()
- Когда вы инициализируете
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)
}
}
Это создает представление, и перед его отображением сетка устанавливается в правильное значение.
- Инициализатор сообщения «ScenarioView» недоступен из-за «частного» уровня защиты кажется очевидным: вы должны указать значение для переменной
scenarios
(у него нет значения по умолчанию), но он помечен как . Удалятьprivate
.
@ObservedObject var scenarios: OutputOverview
Затем не забудьте передать значение типаOutputOverview
для переменной при вызове представления:
ScenarioView(scenarios: aVariableOfTypeOutputOverview)
- Ошибка несоответствия типа, которую вы получаете внутри
@main
код тоже понятен - вы определили переменную типаScenario
:
@State var scenarios = Scenario(scenarioNumber: 1, date: Date.now)
ноScenarioView
требуется другой тип:
@ObservedObject private var scenarios: OutputOverview
Один из них нуждается в изменении, чтобы ваш код работал.