SwiftUI .sheet() 的主体在解雇后被评估并且不呈现

Posted

技术标签:

【中文标题】SwiftUI .sheet() 的主体在解雇后被评估并且不呈现【英文标题】:Body of a SwiftUI .sheet() is being evaluated after dismiss and not presenting 【发布时间】:2021-10-07 17:56:26 【问题描述】:

用例

如果您有一个 SwiftUI ContentView(),它基于 @State 属性 presentSheet 显示 PausableView(),该属性也用于呈现 .sheet(),则根据 presentSheet 的方式对正文进行不同的评估属性在 ContentView 的主体内使用。

struct ContentView: View 
    @State private var presentSheet = false
    
    var body: some View 
        return VStack
            Button("Show Sheet") 
                presentSheet.toggle()
            
            PausableView(isPaused: presentSheet) // 1. passing the property as a normal variable
                                                 // evaluates the body and the sheet on dismiss
//           PausableView(isPaused: $presentSheet) // 2. passing the property as a @Binding
                                                   // doesn't evaluate the body when it changes on dismiss
        
        .sheet(isPresented: $presentSheet) 
            DismissingView(isPresented: $presentSheet)
        
    

    如果该属性作为普通属性发送到PausableView(isPaused: presentSheet),则在关闭工作表时会评估 ContentView 的正文和工作表的正文

    如果属性以@Binding 的形式发送到PausableView(isPaused: $presentSheet),则在关闭工作表时不会评估 ContentView 的正文和工作表的正文

这是正常行为吗?

当工作表在关闭后不再呈现时,是否应该评估工作表的正文?

另外,使用@Binding 似乎完全改变了视图主体的评估方式。但是将其作为@Binding 发送是不正确的,因为该属性在子视图中应该是只读的。

视觉

1 - 使用普通属性时会评估正文(参见第 27-28 和 53-54 行):

2 - 使用 @Binding 时不评估正文(参见第 27-28 和 53-54 行):

示例项目

在 Xcode 13 中创建的示例项目可在此处获得:https://github.com/clns/SwiftUI-sheet-redraw-on-dismiss。我注意到 ios 14 和 iOS 15 上的行为相同。

相关代码在ContentView.swift:

import SwiftUI

struct DismissingView: View 
    @Binding var isPresented: Bool
    
    var body: some View 
        if #available(iOS 15.0, *) 
            print(Self._printChanges())
         else 
            print("DismissingView: body draw")
        
        return VStack 
            Button("Dismiss")  isPresented.toggle() 
            Text("Dismissing Sheet").padding()
        .background(Color.white)
    


struct PausableView: View 
    var isPaused: Bool
//    @Binding var isPaused: Bool
    
    private let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
    @State private var counter = 0
    
    var body: some View 
        Text("Elapsed seconds: \(counter)")
            .onReceive(timer)  _ in
                counter += isPaused ? 0 : 1
            
    


struct ContentView: View 
    @State private var presentSheet = false
    
    var body: some View 
        if #available(iOS 15.0, *) 
            print(Self._printChanges())
         else 
            print("ContentView: body draw")
        
        return VStack
            Button("Show Sheet")  presentSheet.toggle() 
            Text("The ContentView's body along with the .sheet() is being redrawn immediately after dismiss, if the @State property `presentSheet` is used anywhere else in the view - e.g. passed to `PausableView(isPaused:presentSheet)`.\n\nBut if the property is passed as a @Binding to `PausableView(isPaused:$presentSheet)`, the ContentView's body is not redrawn.").padding()
            PausableView(isPaused: presentSheet)
//            PausableView(isPaused: $presentSheet)
        
        .sheet(isPresented: $presentSheet) 
            DismissingView(isPresented: $presentSheet)
                .background(BackgroundClearView()) // to see what's happening under the sheet
        
    

也发布在 Apple 开发者论坛上:https://developer.apple.com/forums/thread/691783

【问题讨论】:

【参考方案1】:

使用@Binding 网络正确的行为。

使用普通属性时被评估的主体似乎是一个错误,因为结构应该是不可变的......除非你使用像@State/@Binding/等这样的属性包装器。

另外,使用@Binding 似乎完全改变了视图的方式 身体被评估。但是将其作为@Binding 发送是不正确的,因为 该属性在子视图中应该是只读的。 为什么你认为它不正确?使用 @Binding 意味着您读取和修改传递给子视图的数据,但您的子视图并不“拥有”它。

/Xcode 12 用户在这里

【讨论】:

以上是关于SwiftUI .sheet() 的主体在解雇后被评估并且不呈现的主要内容,如果未能解决你的问题,请参考以下文章

停发薪水三个月后被解雇,程序员删除系统数据予以反击

观察到的对象无意中被解雇 SwiftUI

SwiftUI:为 .sheet(_: onDismiss:) 重置状态

SwiftUI禁止用户关闭sheet弹出视图在iOS14.6+失效的巧妙解决

SwiftUI禁止用户关闭sheet弹出视图在iOS14.6+失效的巧妙解决

SwiftUI:.sheet() 在关闭当前工作表时不会使用预期数据转到上一个视图