Передача вычисляемой переменной в другое представление
Я пытаюсь создать свою собственную сетку, размер которой изменяется под каждый элемент. Это нормально. Вот код
GeometryReader { geo in
let columnCount = Int((geo.size.width / 250).rounded(.down))
let tr = CDNresponse.data?.first?.translations ?? []
let rowsCount = (CGFloat(tr.count) / CGFloat(columnCount)).rounded(.up)
LazyVStack {
ForEach(0..<Int(rowsCount), id: \.self) { row in // create number of rows
HStack {
ForEach(0..<columnCount, id: \.self) { column in // create 3 columns
let index = row * columnCount + column
if index < (tr.count) {
VStack {
MovieCellView(index: index, tr: tr, qualities: $qualities)
}
}
}
}
}
}
}
Но поскольку я не знаю точное количество элементов и их индексы, мне нужно их вычислить с учетом.
let index = row * columnCount + column
И вот в чем проблема - если я их передам как обычно (
MovieCellView(index: index ... )
) при изменении индекса новое значение не передается для просмотра. Я не могу использовать @State и @Binding, так как я не могу объявить его непосредственно в View Builder и не могу объявить его в структуре, потому что я не знаю счетчика. Как правильно передавать данные?
Код:
struct MovieCellView: View {
@State var index: Int
@State var tr: [String]
@State var showError: Bool = false
@State var detailed: Bool = false
@Binding var qualities: [String : [Int : URL]]
var body: some View {
...
}
}
Самый простой пример, только что добавленный в VStack с помощью MovieCellView и
Text("\(index)")
в
MovieCellView
тело. Индекс в VStack всегда меняется, но не в MovieCellView. Надо уточнить, у меня приложение на macOS и размер окна изменен
4 ответа
Это мое решение (тестовый код) вашей проблемы передачи вычисляемой переменной в другое представление. Я использую ObservableObject для хранения информации, необходимой для достижения того, что вам нужно.
import SwiftUI
@main
struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
class MovieCellModel: ObservableObject {
@Published var columnCount: Int = 0
@Published var rowCount: Int = 0
// this could be in the view
func index(row: Int, column: Int) -> Int {
return row * columnCount + column
}
}
struct ContentView: View {
@StateObject var mcModel = MovieCellModel()
@State var qualities: [String : [Int : URL]] = ["":[1: URL(string: "https://example.com")!]] // for testing
let tr = CDNresponse.data?.first?.translations ?? [] // for testing
var body: some View {
VStack {
GeometryReader { geo in
LazyVStack {
ForEach(0..<Int(mcModel.rowCount), id: \.self) { row in // create number of rows
HStack {
ForEach(0..<mcModel.columnCount, id: \.self) { column in // create 3 columns
if mcModel.index(row: row, column: column) < (tr.count) {
VStack {
MovieCellView(index: mcModel.index(row: row, column: column), tr: tr, qualities: $qualities)
}
}
}
}
}
}.onAppear {
mcModel.columnCount = Int((geo.size.width / 250).rounded(.down))
mcModel.rowCount = Int((CGFloat(tr.count) / CGFloat(mcModel.columnCount)).rounded(.up))
}
}
}
}
}
struct MovieCellView: View {
@State var index: Int
@State var tr: [String]
@Binding var qualities: [String : [Int : URL]]
@State var showError: Bool = false
@State var detailed: Bool = false
var body: some View {
Text("\(index)").foregroundColor(.red)
}
}
Поскольку ни один из ответов не помог, а только один сработал наполовину, я решил проблему сам. Вам нужно создать привязку, а затем просто передать ее
var videos: some View {
GeometryReader { geo in
let columnCount = Int((geo.size.width / 250).rounded(.down))
let rowsCount = (CGFloat((CDNresponse.data?.first?.translations ?? []).count) / CGFloat(columnCount)).rounded(.up)
LazyVStack {
ForEach(0..<Int(rowsCount), id: \.self) { row in // create number of rows
HStack {
ForEach(0..<columnCount, id: \.self) { column in // create 3 columns
let index = row * columnCount + column
if index < ((CDNresponse.data?.first?.translations ?? []).count) {
MovieCellView(translation: trBinding(for: index), qualities: qualityBinding(for: (CDNresponse.data?.first?.translations ?? [])[index]))
}
}
}
}
}
}
}
private func qualityBinding(for key: String) -> Binding<[Int : URL]> {
return .init(
get: { self.qualities[key, default: [:]] },
set: { self.qualities[key] = $0 })
}
private func trBinding(for key: Int) -> Binding<String> {
return .init(
get: { (self.CDNresponse.data?.first?.translations ?? [])[key] },
set: { _ in return })
}
struct MovieCellView: View {
@State var showError: Bool = false
@State var detailed: Bool = false
@Binding var translation: String
@Binding var qualities: [Int : URL]
var body: some View {
...
}
}
Если ваша коллекция изменится, ваше представление должно быть пересчитано, и старое представление со старым индексом будет отклонено. Как может измениться индекс? Объясните, пожалуйста, что именно не работает?
CDNresponse.data?.first?.translations
это похоже на синглтон. Если вы обновите его значение и ожидаете, что ваше представление тоже будет обновлено, этого не произойдет.
Единственная переменная, которая изменяется за пределами
ForEach
в вашей
index
расчет есть.
row
и
column
- это просто индексы из циклов. Таким образом, вы можете объявить
@State
переменная в родительском представлении, поэтому, когда она изменяется, родительское представление обновится, и, следовательно, все ячейки также обновятся с новым
columnCount
.
@State private var columnCount: CGFloat = 0
var body: some View {
GeometryReader { geo in
columnCount = Int((geo.size.width / 250).rounded(.down))
let tr = CDNresponse.data?.first?.translations ?? []
let rowsCount = (CGFloat(tr.count) / CGFloat(columnCount)).rounded(.up)
LazyVStack {
ForEach(0..<Int(rowsCount), id: \.self) { row in // create number of rows
HStack {
ForEach(0..<columnCount, id: \.self) { column in // create 3 columns
let index = row * columnCount + column
if index < (tr.count) {
VStack {
MovieCellView(index: index, tr: tr, qualities: $qualities)
}
}
}
}
}
}
}
}