为啥此 SwiftUI 代码在目标屏幕中显示“空”?
Posted
技术标签:
【中文标题】为啥此 SwiftUI 代码在目标屏幕中显示“空”?【英文标题】:Why does this SwiftUI code show "Empty" in the destination screen?为什么此 SwiftUI 代码在目标屏幕中显示“空”? 【发布时间】:2021-06-23 14:36:02 【问题描述】:struct ContentView: View
@State var showModal = false
@State var text = "Empty"
var body: some View
Button("show text")
text = "Filled"
showModal = true
.sheet(isPresented: $showModal)
VStack
Text(text)
Button("print text")
print(text)
我以为当点击“显示文本”按钮时,text
的值将设置为“已填充”,showModal
将设置为 true,这样sheet
中指定的屏幕将是显示,并且该屏幕上将显示“已填充”一词。
我以为它会显示“已填充”,但实际上显示“空”。
此外,当我使用print text
按钮打印text
时,控制台显示“已填充”。
为什么会这样?
当我点击目标屏幕上的按钮时,我缺少什么来显示我设置的值?
使用 Xcode12.4、Xcode12.5
添加新模式的代码。
struct ContentView: View
@State var number = 0
@State var showModal = false
var body: some View
VStack
Button("set number 1")
number = 1
showModal = true
print("set number = \(number)")
Button("set number 2")
number = 2
showModal = true
print("set number = \(number)")
Button("add number")
number += 1
showModal = true
print("add number = \(number)")
.sheet(isPresented: $showModal)
VStack
let _ = print("number = \(number)")
Text("\(number)")
在上面的代码中,当我第一次点击“set number 1”或“set number 2”时,目标屏幕显示“0”。无论您点击多少次同一个按钮,都会显示“0”。
但是,如果您在点击“设置编号 1”后点击“设置编号 2”,它将正常工作并显示“2”。如果您继续点击“设置数字1”,将显示“1”,应用程序将正常运行。
应用启动后第一次点击“加号”时,仍然会显示“0”,但如果再次点击“加号”,则会显示“2”并且应用会计数正确。
这说明即使@State变量更新了也可以启动目标屏幕的渲染,但是只有在目标屏幕中首先引用了@State变量时,它似乎没有被正确引用。
谁能解释它为什么会这样?
或者这看起来像 SwiftUI 中的错误?
【问题讨论】:
我认为 SwiftUI 会预加载表格 - 请参阅 ***.com/questions/65281559/… @aheze 谢谢你的建议。与链接中的代码不同,我的代码在转换屏幕中使用@State 变量text
。如果它正在预加载,为什么它没有检测到text
的变化并重新加载屏幕?我怎样才能让它重新加载?
这能回答你的问题吗? SwiftUI: Understanding .sheet / .fullScreenCover lifecycle when using constant vs @Binding initializers
如果您使用预计值,您将看到变化。它不会显示更改,因为显示工作表会阻止重新加载结构。它只是坐在那里等到重新加载。您可以通过在工作表内容中使用@Binding
或使用以item
作为参数的工作表来获取最新值
【参考方案1】:
从 iOS 14 开始,有几种主要的方式来呈现Sheet
。
首先,对于您的示例,您需要创建一个单独的 View
并将您的属性传递给 Binding
,然后在出现 Sheet
时正确更新。
// ContentView
Button ...
.sheet(isPresented: $showModal)
SheetView(text: $text)
struct SheetView: View
@Binding var text: String
var body: some View
VStack
Text(text)
Button("print text")
print(text)
另一种方法是使用Optional
可识别对象,当该对象具有值时,将显示工作表。这样做,您不需要单独管理工作表是否显示的状态。
struct Item: Identifiable
var id = UUID()
var text: String
struct ContentView: View
@State var item: Item? = nil
var body: some View
Button("show text")
item = Item(text: "Filled")
.sheet(item: $item, content: item in
VStack
Text(item.text)
Button("print text")
print(item.text)
)
【讨论】:
感谢您的建议。我已经知道如果我将View
分开并使用@Binding
传递它,它会很好地工作。但是,我不相信使用 @State 将不起作用。我在问题中添加了另一个新代码。你能解释一下为什么你添加的代码会这样吗?
这是因为view第一次加载的时候,是根据view的当前状态预加载sheetclosure。呈现视图并将其关闭后,状态会刷新,这就是为什么您仅在第一次呈现工作表时才看到问题。我不确定这是否是预期行为,可能值得报告这个问题。
感谢您的解释。不知道为什么,但是我明白了,预加载的第一张表无法获取状态变量的更新值并显示更新前的值,而关闭并重新显示的表可以获取更新后的值状态变量,因此此后它可以正常工作。基于此,我尝试了很多事情,并找到了一种在显示的第一张表上显示状态变量的更新值的方法。我已经对这种方法做出了自我回答。不过,我觉得这个方法有点棘手。
否则(无论如何对于 ios 13),解决方案是将 item 属性从视图移动到模型。使其成为 ObervableObject 类中的 Published 属性,而不是视图中的 State 属性。这确实是因为工作表是从视图结构中预加载的,并且看不到结构内部的更改(因为更改发生在预加载工作表之后)。【参考方案2】:
通过添加以下代码,我能够在目标屏幕中显示“已填充”一词。
将updateDetector
声明为@State 变量。
更新工作表中视图的updateDetector
onAppear。
在工作表的视图中的某处引用updateDetector
。
struct ContentView: View
@State var showModal = false
@State var updateDetector = false
@State var text = "Empty"
var body: some View
Button("show text")
text = "Filled"
print("set text to \(text)")
showModal = true
.sheet(isPresented: $showModal)
let _ = print("render text = \(text)")
VStack
Text(text)
// use EmptyView to access updateDetector
if updateDetector
EmptyView()
.onAppear
// update updateDetector
print("toggle updateDetector")
updateDetector.toggle()
那么,在渲染完预加载时的初始值后,更新updateDetector
onAppear的过程就会起作用,再次渲染View,并显示@State变量的更新值。
当您点击上述代码中的“显示文本”按钮时,控制台中将显示以下内容,屏幕上将显示“已填充”。
set text to Filled
render text = Empty
toggle updateDetector
render text = Filled
我仍然不相信为什么我无法在第一个预加载的工作表中获得 @State 变量的更新值。所以我会通过反馈助手向 Apple 报告。
【讨论】:
以上是关于为啥此 SwiftUI 代码在目标屏幕中显示“空”?的主要内容,如果未能解决你的问题,请参考以下文章
为啥 SwiftUI UIHostingController 有额外的间距?
SwiftUI Preview 显示错误 - 无法在此文件中预览 - 当前目标需要调整构建设置
为啥我的视图在 swiftUI 中首次出现在屏幕上时没有转换