自定义视图不会使用通过绑定提供的状态变量更新,但调试手表会显示更改
Custom view won't use state variable update provided through binding, but debug watch shows changes
我开始了解 SwiftUI 的 @Binding 和 @State 方式。或者至少我喜欢这样想。无论如何,有一些调试结果让我很困惑。让我解释一下。
这里的objective是控制一个"floating"视图在ContentView上的位置。这涉及将 @binding 变量发送到 ContentView 中的 @State 的消息。这有效:您可以在调试器中看到它。预期结果是一个浮动矩形,当按下齿轮按钮时它会改变屏幕中的位置。
浮动视图可以通过自己的@State 来控制'where' 它浮动(在'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
没有错误,编译良好,变量通过。可以对浮动视图进行硬编码传递值,并且矩形会响应,但如果以编程方式提供值则不会。
只需在 TestView 中删除 @State var aposition
中的 @State
。基本上,@State 变量是为了表示真实的来源,所以它们永远不会从更高的视图传递值。
关于绑定的工作原理,我可以写很长的解释,但在 WWDC 会话数据流与 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 )
}
}
我开始了解 SwiftUI 的 @Binding 和 @State 方式。或者至少我喜欢这样想。无论如何,有一些调试结果让我很困惑。让我解释一下。
这里的objective是控制一个"floating"视图在ContentView上的位置。这涉及将 @binding 变量发送到 ContentView 中的 @State 的消息。这有效:您可以在调试器中看到它。预期结果是一个浮动矩形,当按下齿轮按钮时它会改变屏幕中的位置。
浮动视图可以通过自己的@State 来控制'where' 它浮动(在'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
没有错误,编译良好,变量通过。可以对浮动视图进行硬编码传递值,并且矩形会响应,但如果以编程方式提供值则不会。
只需在 TestView 中删除 @State var aposition
中的 @State
。基本上,@State 变量是为了表示真实的来源,所以它们永远不会从更高的视图传递值。
关于绑定的工作原理,我可以写很长的解释,但在 WWDC 会话数据流与 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 )
}
}