【问题标题】:Passing calculated variable to another View将计算变量传递给另一个视图
【发布时间】:2021-08-09 07:58:56
【问题描述】:

我正在尝试创建自己的网格,它可以调整每个元素的大小。没关系。这是一个代码

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 上并且窗口已调整大小

【问题讨论】:

    标签: swift swiftui swiftui-view


    【解决方案1】:

    这是我对您将计算变量传递到另一个视图的问题的解决方案(测试代码)。 我使用 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)
        }
    }
    

    【讨论】:

    • 我使用此解决方案的每个单元格都有相同的索引
    • 是的,抱歉,我没看到。我已经更新了我的答案。
    • 这让我摆脱了网格的主要优势——元素交换和动态调整大小。
    • 这是否意味着这对您不起作用?
    • 是的,因为 columnCount 和 rowCount 不会改变
    【解决方案2】:

    在您的index 计算中,在ForEach 之外发生变化的唯一变量是columnCountrowcolumn 只是循环中的索引。因此,您可以在父视图上将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)
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    

    【讨论】:

    • 状态变量不能在视图上修改,所以这个不起作用
    • @Higherous 你真的尝试过这段代码吗?状态变量旨在从视图主体进行修改,这是它们的主要目的。它们不能从视图外部修改,但可以从视图内部修改。
    • 你确定吗?只需尝试使用相同用法的最小轻量级代码struct ContentView: View { @State var myVar: Int = 0 var body: some View { myVar = 5 Text("Hello, World!") .padding() } } 状态变量只能通过 onAppear 等函数进行更改
    • @Higherous 这是完全错误的,不知道你为什么会得到这个想法。我完全确定从视图主体内部更改 State 变量是安全的。
    • 这是不可能的。上面的代码我掉了,自己试试 Pastebin pastebin.com/qiQ1Y79z
    【解决方案3】:

    由于没有一个答案有效,只有一个成功了一半,我自己解决了这个问题。你需要生成一个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] = $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 {
            ...
        }
        
    }
    

    【讨论】:

      猜你喜欢
      • 2013-05-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-11-18
      • 2011-10-01
      相关资源
      最近更新 更多