为啥此 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()
            
        
    

那么,在渲染完预加载时的初始值后,更新updateDetectoronAppear的过程就会起作用,再次渲染View,并显示@State变量的更新值。

当您点击上述代码中的“显示文本”按钮时,控制台中将显示以下内容,屏幕上将显示“已填充”。

set text to Filled
render text = Empty
toggle updateDetector
render text = Filled

我仍然不相信为什么我无法在第一个预加载的工作表中获得 @State 变量的更新值。所以我会通过反馈助手向 Apple 报告。

【讨论】:

以上是关于为啥此 SwiftUI 代码在目标屏幕中显示“空”?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 SwiftUI UIHostingController 有额外的间距?

SwiftUI Preview 显示错误 - 无法在此文件中预览 - 当前目标需要调整构建设置

为啥我的视图在 swiftUI 中首次出现在屏幕上时没有转换

SwiftUI 为啥不能在视图之间传递发布者?

从目标视图返回时,SwiftUI NavigationLink 显示为突出显示

FireBase 登录过程后导航到主屏幕(内容视图)。 (SwiftUI)