Sheet 删除标签时无法关闭
Sheet can't be dismissed when removing a tab
在我的应用程序中,我有两个选项卡。第二个选项卡根据某些条件显示或隐藏。我发现当要隐藏选项卡时,如果在第二个选项卡中显示 sheet,则无法关闭 sheet。
使用以下代码可以一致地重现该问题。要重现它,请单击选项卡 2,然后单击“Present Sheet”,然后单击“隐藏选项卡 2”。您会看到 sheet 没有被删除,尽管包含它的选项卡(即选项卡 2)已被删除(您可以将 sheet 向下拖动以验证它)。
对我来说这似乎是一个 SwiftUI 错误。有谁知道如何解决它?我即将完成我的应用程序,但遇到了这个意想不到的问题 :( 非常感谢任何帮助。
struct ContentView: View {
@State var showTab2: Bool = true
var body: some View {
TabView {
// tab 1
NavigationView {
Text("Tab 1")
}
.tabItem {
Label("Tab 1", systemImage: "1.circle")
}
// tab 2
if showTab2 {
NavigationView {
Tab2(showTab2: $showTab2)
}
.tabItem {
Label("Tab 2", systemImage: "2.circle")
}
}
}
}
}
struct Tab2: View {
@State var showSheet: Bool = false
@Binding var showTab2: Bool
var body: some View {
VStack(spacing: 12) {
Text("Tab 2")
Button("Click to present sheet") {
showSheet = true
}
}
.sheet(isPresented: $showSheet, onDismiss: nil) {
NavigationView {
MySheet(showTab2: $showTab2)
}
}
}
}
struct MySheet: View {
@Environment(\.dismiss) var dismiss
@Binding var showTab2: Bool
var body: some View {
Button("Click to hide tab 2") {
// dismiss() works fine if I comment out this line.
showTab2 = false
dismiss()
}
}
}
我已经向 Apple 提交了对此的反馈,但我对任何答复都不乐观(我从未收到过)。
更新:
此问题可以在许多其他不涉及 sheet 的情况下重现。所以,@Asperi 给出的第二种方法不是通用的解决方案。
好吧,这里我们看到了动作冲突(由于赛车):异步 sheet 关闭(由于动画)和同步选项卡删除。
以下是可能的方法:
- 延迟选项卡在 sheet 关闭后删除(隐式方式)
Button("Click to hide tab 2") {
dismiss()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) { // << here !!
showTab2 = false
}
}
- 在 sheet 关闭后删除选项卡(显式方式)
.sheet(isPresented: $showSheet, onDismiss: { showTab2 = false }) { // << here !!
NavigationView {
MySheet(showTab2: $showTab2)
}
}
注意:实际上当视图 knows/manages 父级的父级的东西不是很好的设计,所以选项 2(可能有一些额外的 conditions/callbacks)更可取。
@Asperi 给出了很好的答案。但将他的方法应用到实际应用程序中并不简单。我将在下面解释为什么以及如何做到这一点。
Asperi 方法的关键思想是,由于 UI 更改具有竞争条件,因此应分两步执行。在这两种方法中,sheet 首先被关闭,然后选项卡被隐藏。
然而,在实践中,如何分离这两个步骤可能并不明显。例如,我的应用程序是这样工作的(我认为这是典型的):
- sheet 包含一个表单并调用数据模型 API 以在用户提交表单时改变数据模型。
- 由于数据模型 API 可能会失败,因此 sheet 不会在用户提交表单后立即自行关闭。相反,它仅在 API 调用成功时执行此操作(API 调用是同步的)。
- 当数据模型发生变异时,可能会触发隐藏选项卡的条件。
注意第 2 项和第 3 项。这意味着 sheet 必须先调用数据模型 API,这可能会隐藏选项卡,然后自行关闭。
我花了一段时间才想出解决方案 - 引入一个专用状态来控制 show/hide 选项卡,从而分离这两个步骤。现在剩下的问题是如何将数据模型更改同步到该状态。由于目的是使它们显示为对 UI 的两个单独更改,因此我们不能使用 Combine。如果不实现它可能会很混乱 属性 因为数据模型可以从任何地方发生变化(例如 Form、ActionSheet 或只是 Button)。幸运的是我找到了一个非常优雅的方法:
.onChange(of: model.showTab2) { value in
// In my experiments async() works fine, but just to be on the safe side...
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
// This is a state outside data model. It hides/shows tab2.
showTab2 = value
}
}
这又是一个例子,没有加一层抽象解决不了的问题:)
在我的应用程序中,我有两个选项卡。第二个选项卡根据某些条件显示或隐藏。我发现当要隐藏选项卡时,如果在第二个选项卡中显示 sheet,则无法关闭 sheet。
使用以下代码可以一致地重现该问题。要重现它,请单击选项卡 2,然后单击“Present Sheet”,然后单击“隐藏选项卡 2”。您会看到 sheet 没有被删除,尽管包含它的选项卡(即选项卡 2)已被删除(您可以将 sheet 向下拖动以验证它)。
对我来说这似乎是一个 SwiftUI 错误。有谁知道如何解决它?我即将完成我的应用程序,但遇到了这个意想不到的问题 :( 非常感谢任何帮助。
struct ContentView: View {
@State var showTab2: Bool = true
var body: some View {
TabView {
// tab 1
NavigationView {
Text("Tab 1")
}
.tabItem {
Label("Tab 1", systemImage: "1.circle")
}
// tab 2
if showTab2 {
NavigationView {
Tab2(showTab2: $showTab2)
}
.tabItem {
Label("Tab 2", systemImage: "2.circle")
}
}
}
}
}
struct Tab2: View {
@State var showSheet: Bool = false
@Binding var showTab2: Bool
var body: some View {
VStack(spacing: 12) {
Text("Tab 2")
Button("Click to present sheet") {
showSheet = true
}
}
.sheet(isPresented: $showSheet, onDismiss: nil) {
NavigationView {
MySheet(showTab2: $showTab2)
}
}
}
}
struct MySheet: View {
@Environment(\.dismiss) var dismiss
@Binding var showTab2: Bool
var body: some View {
Button("Click to hide tab 2") {
// dismiss() works fine if I comment out this line.
showTab2 = false
dismiss()
}
}
}
我已经向 Apple 提交了对此的反馈,但我对任何答复都不乐观(我从未收到过)。
更新:
此问题可以在许多其他不涉及 sheet 的情况下重现。所以,@Asperi 给出的第二种方法不是通用的解决方案。
好吧,这里我们看到了动作冲突(由于赛车):异步 sheet 关闭(由于动画)和同步选项卡删除。
以下是可能的方法:
- 延迟选项卡在 sheet 关闭后删除(隐式方式)
Button("Click to hide tab 2") {
dismiss()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) { // << here !!
showTab2 = false
}
}
- 在 sheet 关闭后删除选项卡(显式方式)
.sheet(isPresented: $showSheet, onDismiss: { showTab2 = false }) { // << here !!
NavigationView {
MySheet(showTab2: $showTab2)
}
}
注意:实际上当视图 knows/manages 父级的父级的东西不是很好的设计,所以选项 2(可能有一些额外的 conditions/callbacks)更可取。
@Asperi 给出了很好的答案。但将他的方法应用到实际应用程序中并不简单。我将在下面解释为什么以及如何做到这一点。
Asperi 方法的关键思想是,由于 UI 更改具有竞争条件,因此应分两步执行。在这两种方法中,sheet 首先被关闭,然后选项卡被隐藏。
然而,在实践中,如何分离这两个步骤可能并不明显。例如,我的应用程序是这样工作的(我认为这是典型的):
- sheet 包含一个表单并调用数据模型 API 以在用户提交表单时改变数据模型。
- 由于数据模型 API 可能会失败,因此 sheet 不会在用户提交表单后立即自行关闭。相反,它仅在 API 调用成功时执行此操作(API 调用是同步的)。
- 当数据模型发生变异时,可能会触发隐藏选项卡的条件。
注意第 2 项和第 3 项。这意味着 sheet 必须先调用数据模型 API,这可能会隐藏选项卡,然后自行关闭。
我花了一段时间才想出解决方案 - 引入一个专用状态来控制 show/hide 选项卡,从而分离这两个步骤。现在剩下的问题是如何将数据模型更改同步到该状态。由于目的是使它们显示为对 UI 的两个单独更改,因此我们不能使用 Combine。如果不实现它可能会很混乱 属性 因为数据模型可以从任何地方发生变化(例如 Form、ActionSheet 或只是 Button)。幸运的是我找到了一个非常优雅的方法:
.onChange(of: model.showTab2) { value in
// In my experiments async() works fine, but just to be on the safe side...
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
// This is a state outside data model. It hides/shows tab2.
showTab2 = value
}
}
这又是一个例子,没有加一层抽象解决不了的问题:)