swift - 自定义 View 不会使用通过绑定(bind)提供的状态变量更新,但调试监视会显示更改

标签 swift swiftui

我开始了解 SwiftUI 的 @Binding 和 @State 方式。或者至少我喜欢这么认为。不管怎样,有一些调试结果让我很困惑。让我解释一下。

这里的目标是控制 ContentView 上“ float ” View 的位置。这涉及到一条消息,将 @binding 变量发送到 ContentView 中的 @State。这是有效的:您可以在调试器中看到它。预期结果是一个 float 矩形,当按下齿轮按钮时,该矩形会改变屏幕中的位置。

float View 可以传递它自己的@State来控制它 float 的“位置”(“y”坐标的高或低)。如果 ViewPosition 是硬编码传递的,则此方法有效。

现在的问题是,如果您观察在调试器中传递的值,则拼图效果很好,但事实是 floatong View 始终使用相同的值来处理。怎么会这样?

我们可以在所附代码中看到效果,如果监视替代情况,则在第 120 和 133 行设置断点;如果监视默认情况,则在第 76 行设置断点。

代码被剪切粘贴到选项卡式 swiftui 应用程序的新项目中。

我尝试了为两个不同的 ContentView 选项提供的两种粗略方法(重命名以更改执行分支)。观察调试器中的变量非常重要,以便享受完整的拼图体验,因为 .high 和 .low 值已顺利通过,但矩形仍保持静止。

//
//  ContentView.swift
//  TestAppUno
//


import SwiftUI


struct MenuButton1: View {
    @Binding var menuButtonAction: Bool
    var title: String = "--"
    var body: some View {
        Button(action: {self.menuButtonAction.toggle()}) {
            Image(systemName:"gear")
                .resizable()
                .imageScale(.large)
                .aspectRatio(1, contentMode: .fit)
                .frame(minWidth: 50, maxWidth: 50, minHeight: 50, maxHeight: 50, alignment: .topLeading)

            }
            .background(Color.white.opacity(0))
            .cornerRadius(5)
            .padding(.vertical, 10)
            .position(x: 30, y: 95)
    }

}

struct MenuButton2: View {
    @Binding var menuButtonAction: ViewPosition
    var title: String = "--"
    var body: some View {
        Button(action: {self.toggler()}) {
            Image(systemName:"gear")
                .resizable()
                .imageScale(.large)
                .aspectRatio(1, contentMode: .fit)
                .frame(minWidth: 50, maxWidth: 50, minHeight: 50, maxHeight: 50, alignment: .topLeading)

            }
            .background(Color.white.opacity(0))
            .cornerRadius(5)
            //.border(Color.black, width: 1)
            .padding(.vertical, 10)
            .position(x: 30, y: 95)

    }

    func toggler()->ViewPosition {
        if (self.menuButtonAction == ViewPosition.high) { self.menuButtonAction = ViewPosition.low; return ViewPosition.low } else { self.menuButtonAction = ViewPosition.high; return ViewPosition.low }
    }

}

struct ContentView: View {

    @State private var selection = 0
    @State var moveCard = false
    @State var vpos = ViewPosition.low


    var body: some View {

        TabbedView(selection: $selection){

            ZStack() {


                MenuButton2(menuButtonAction: $vpos)

                //if(self.moveCard){self.vpos = ViewPosition.low} else {self.vpos = ViewPosition.low }
                    // Correct answer, change 1 of 3
                    //TestView(aposition: $vpos) {    // <-- OK
                    TestView(aposition:self.vpos) {

                        VStack(alignment: HorizontalAlignment.center, spacing: 1.0){

                            Text("See here")
                                .font(.headline)

                            }
                            .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .top)

                    }


                }
                .tabItemLabel(Image("first"))
                .tag(0)

            Text("Nothing here")
                .tabItemLabel(Image("second"))
                .tag(1)
        }


    }
}

                    // Correct answer, change 2 of 3
struct ContentView1: View { // <-- Remove this block 

    @State private var selection = 0
    @State var moveCard = false
    @State var cardpos = ViewPosition.low


    var body: some View {

        TabbedView(selection: $selection){

            ZStack() {

                MenuButton1(menuButtonAction: $moveCard)

                if(self.moveCard){

                    TestView(aposition:ViewPosition.low) {

                    VStack(alignment: HorizontalAlignment.center, spacing: 1.0){

                        Text("See here")
                            .font(.headline)

                        }
                        .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .top)

                }
                }else{

                    TestView(aposition:ViewPosition.high) {

                        VStack(alignment: HorizontalAlignment.center, spacing: 1.0){

                            Text("See here")
                                .font(.headline)

                            }
                            .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .top)

                    }
                }

                }
                .tabItemLabel(Image("first"))
                .tag(0)

            Text("Nothing here")
                .tabItemLabel(Image("second"))
                .tag(1)
        }


    }
}



struct TestView<Content: View> : View {
    @State var aposition : ViewPosition
    //proposed solution #1
    //var aposition : ViewPosition
    //proposed solution #2 -> Correct
    //@Binding var aposition : ViewPosition // <- Correct answer, change 3 of 3

    var content: () -> Content
    var body: some View {

        print("Position: " + String( format: "%.3f", Double(self.aposition.rawValue)))


        return Group {
            self.content()
            }
            .frame(height: UIScreen.main.bounds.height/2)
            .frame(width: UIScreen.main.bounds.width)
            .background(Color.red)
            .cornerRadius(10.0)
            .shadow(color: Color(.sRGBLinear, white: 0, opacity: 0.13), radius: 10.0)
            .offset(y: self.aposition.rawValue )


    }


}



enum ViewPosition: CGFloat {
    case high = 50
    case low = 500
}


#if DEBUG
struct ContentView_Previews : PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
#endif

没有错误,编译良好并且变量已传递。可以对 float View 进行硬编码传递值,并且矩形会做出响应,但如果以编程方式提供值则不会。

最佳答案

只需删除 TestView 中的 @State var aposition 中的 @State 即可。基本上,@State 变量旨在表示事实来源,因此它们永远不会从更高的 View 传递值。

我可以写一篇关于绑定(bind)如何工作的很长的解释,但它在 WWDC session Data Flow with SwiftUI 中得到了完美的解释。只需 37 分钟,最终将为您节省大量时间。它明确区分了何时需要使用:@State、@Binding、@BindingObject 和 @EnvironmentObject。

struct TestView<Content: View> : View {
    var aposition : ViewPosition

    var content: () -> Content
    var body: some View {

        print("Position: " + String( format: "%.3f", Double(self.aposition.rawValue)))


        return Group {
            self.content()
        }
        .frame(height: UIScreen.main.bounds.height/2)
            .frame(width: UIScreen.main.bounds.width)
            .background(Color.red)
            .cornerRadius(10.0)
            .shadow(color: Color(.sRGBLinear, white: 0, opacity: 0.13), radius: 10.0)
            .offset(y: self.aposition.rawValue )
    }
}

关于swift - 自定义 View 不会使用通过绑定(bind)提供的状态变量更新,但调试监视会显示更改,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57022388/

相关文章:

swiftui - ScrollView 中的 LazyVStack 自动滚动出现抖动

ios - Swift 属性只有在存在 didSet 时才能正确设置(可以为空)

ios - SwiftUI 组合 : @Published is only available on properties of classes

ios - 为什么 ZStack 只能在我的 ContentView 中工作?

swift - Swift 中的循环链表

ios - 使用指针查询多个 (n=7) 类

ios - 如何从 react native 桥将参数传递给 swift 类

ios - 在返回单个类的类型的闭包中访问 self

ios - 仅从核心数据中获取所需的数据还是所有数据?

ios - 在 SwiftUI 中暂停通知发布者