将计算变量传递给另一个视图
Passing calculated variable to another View
我正在尝试创建自己的网格,它会根据每个元素调整大小。没关系。这是一个代码
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 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
代码:
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 {
...
}
}
最简单的例子
刚刚在带有 MovieCellView 的 VStack 中添加了 Text("\(index)")
,在 MovieCellView
主体中添加了 Text("\(index)")
。 VStack 中的索引总是在变化,但在 MovieCellView 中不会。
有必要澄清一下,我的应用程序是在 macOS 上并且 window 已调整大小
在您的 index
计算中,唯一在 ForEach
之外发生变化的变量是 columnCount
。 row
和 column
只是循环中的索引。因此,您可以在父视图上将 columnCount
声明为 @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)
}
}
}
}
}
}
}
}
这是我针对将计算变量传递给另一个视图的问题的解决方案(测试代码)。
我使用 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)
}
}
由于 none 个答案有效,而且只有一个对半,我自己解决了这个问题。你需要生成一个Binding,然后直接传过去
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] = [=10=] })
}
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 {
...
}
}
我正在尝试创建自己的网格,它会根据每个元素调整大小。没关系。这是一个代码
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 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
代码:
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 {
...
}
}
最简单的例子
刚刚在带有 MovieCellView 的 VStack 中添加了 Text("\(index)")
,在 MovieCellView
主体中添加了 Text("\(index)")
。 VStack 中的索引总是在变化,但在 MovieCellView 中不会。
在您的 index
计算中,唯一在 ForEach
之外发生变化的变量是 columnCount
。 row
和 column
只是循环中的索引。因此,您可以在父视图上将 columnCount
声明为 @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)
}
}
}
}
}
}
}
}
这是我针对将计算变量传递给另一个视图的问题的解决方案(测试代码)。 我使用 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)
}
}
由于 none 个答案有效,而且只有一个对半,我自己解决了这个问题。你需要生成一个Binding,然后直接传过去
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] = [=10=] })
}
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 {
...
}
}