首页 > 解决方案 > 自定义视图不会使用通过绑定提供的状态变量更新,但调试手表会显示更改

问题描述

我开始了解 SwiftUI 的 @Binding 和 @State 方式。或者至少我喜欢这样想。无论如何,有一些调试结果让我很困惑。让我解释。

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

浮动视图可以通过它自己的@State 来控制它浮动的“位置”(“y”坐标中的高或低)。如果 ViewPosition 是硬编码的,则此方法有效。

现在,问题在于,如果您观察在调试器中传递的值,拼图工作得很好,但事实是 floatong 视图总是使用相同的值来处理。怎么会这样?

我们可以在附加代码中看到效果,如果监视替代情况,则在第 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

没有错误,编译良好并且变量通过。可以将硬编码的值传递给浮动视图,并且矩形会响应,但如果以编程方式提供值则不会。

标签: swiftswiftui

解决方案


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

我可以写一个关于绑定如何工作的长篇解释,但它在 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 )
    }
}

推荐阅读