首页 > 解决方案 > SwiftUI .sheet() 的主体在解雇后被评估并且不呈现

问题描述

用例

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

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)
        }
    }
}
  1. 如果该属性 PausableView(isPaused: presentSheet) 作为普通属性发送到 ,则在 关闭工作表时正在评估 ContentView 的正文和工作表的正文

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

这是正常行为吗?

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

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

视觉的

1 - Body 在使用普通属性时被评估(见第 27-28 和 53-54 行):

身体重绘无绑定

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

Body-NOT-redrawn-with-Binding

示例项目

此处提供了在 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

标签: iosswiftuiswiftui-sheet

解决方案


使用 @Binding 可以解决正确的行为。

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

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

/Xcode 12 用户在这里


推荐阅读