在 SwiftUI 中,多个工作表(isPresented:)不起作用

这个 ContentView 有两个不同的模态视图,因此我对两个视图都使用 sheet(isPresented:),但似乎只显示了最后一个视图。我怎样才能解决这个问题呢?或者在 SwiftUI 中不可能在一个视图上使用多个工作表?

struct ContentView: View {
    

@State private var firstIsPresented = false
@State private var secondIsPresented = false
    

var body: some View {
NavigationView {
VStack(spacing: 20) {
Button("First modal view") {
self.firstIsPresented.toggle()
}
Button ("Second modal view") {
self.secondIsPresented.toggle()
}
}
.navigationBarTitle(Text("Multiple modal view problem"), displayMode: .inline)
.sheet(isPresented: $firstIsPresented) {
Text("First modal view")
}
.sheet(isPresented: $secondIsPresented) {
Text("Only the second modal view works!")
}
}
}
}

上面的代码在没有警告的情况下进行编译(Xcode 11.2.1)。

38351 次浏览

请尝试以下代码

更新答案(iOS14,Xcode 12)

enum ActiveSheet {
case first, second
var id: Int {
hashValue
}
}


struct ContentView: View {


@State private var showSheet = false
@State private var activeSheet: ActiveSheet? = .first


var body: some View {
    

NavigationView {
VStack(spacing: 20) {
Button("First modal view") {
self.showSheet = true
self.activeSheet = .first
}
Button ("Second modal view") {
self.showSheet = true
self.activeSheet = .second
}
}
.navigationBarTitle(Text("Multiple modal view problem"), displayMode: .inline)
.sheet(isPresented: $showSheet) {
if self.activeSheet == .first {
Text("First modal view")
}
else {
Text("Only the second modal view works!")
}
}
}
}
}

您的情况可以通过以下方法解决(使用 Xcode 11.2进行测试)

var body: some View {


NavigationView {
VStack(spacing: 20) {
Button("First modal view") {
self.firstIsPresented.toggle()
}
.sheet(isPresented: $firstIsPresented) {
Text("First modal view")
}
Button ("Second modal view") {
self.secondIsPresented.toggle()
}
.sheet(isPresented: $secondIsPresented) {
Text("Only the second modal view works!")
}
}
.navigationBarTitle(Text("Multiple modal view problem"), displayMode: .inline)
}
}

还可以将工作表添加到放置在视图背景中的 EmptyView 中。这可以多次执行:

  .background(EmptyView()
.sheet(isPresented: isPresented, content: content))

可以通过对按钮和。床单在一起。如果你有一个领先,一个落后,就这么简单。但是,如果在前导或尾随中有多个导航条目,则需要在 HStack 中包装它们,并在 VStack 中包装每个按钮及其表调用。

下面是两个拖尾按钮的例子:

            trailing:
HStack {
VStack {
Button(
action: {
self.showOne.toggle()
}
) {
Image(systemName: "camera")
}
.sheet(isPresented: self.$showOne) {
OneView().environment(\.managedObjectContext, self.managedObjectContext)
}
}//showOne vstack


VStack {
Button(
action: {
self.showTwo.toggle()
}
) {
Image(systemName: "film")
}
.sheet(isPresented: self.$showTwo) {
TwoView().environment(\.managedObjectContext, self.managedObjectContext)
}
}//show two vstack
}//nav bar button hstack

除了 Rohit Makwana 的回答之外,我还找到了一种将工作表内容提取到函数中的方法,因为编译器在检查我的大型 View时遇到了困难。

extension YourView {
enum Sheet {
case a, b
}


@ViewBuilder func sheetContent() -> some View {
if activeSheet == .a {
A()
} else if activeSheet == .b {
B()
}
}
}

你可以这样使用它:

.sheet(isPresented: $isSheetPresented, content: sheetContent)

它使代码更加清晰,也减轻了编译器的压力。

在其中创建自定义 Button 视图和调用表解决了这个问题。

struct SheetButton<Content>: View where Content : View {


var text: String
var content: Content
@State var isPresented = false


init(_ text: String, @ViewBuilder content: () -> Content) {
self.text = text
self.content = content()
}


var body: some View {
Button(text) {
self.isPresented.toggle()
}
.sheet(isPresented: $isPresented) {
self.content
}
}
}

ContentView 将更加干净。

struct ContentView: View {


var body: some View {


NavigationView {
VStack(spacing: 20) {
SheetButton("First modal view") {
Text("First modal view")
}
SheetButton ("Second modal view") {
Text("Only the second modal view works!")
}
}
.navigationBarTitle(Text("Multiple modal view problem"), displayMode: .inline)
}
}
}

当打开工作表依赖于 List 行内容时,此方法也能正常工作。

struct ContentView: View {


var body: some View {


NavigationView {
List(1...10, id: \.self) { row in
SheetButton("\(row) Row") {
Text("\(row) modal view")
}
}
.navigationBarTitle(Text("Multiple modal view problem"), displayMode: .inline)
}
}
}

我知道这个问题已经有了很多答案,但我找到了另一个可能的解决方案,我发现这个方案非常有用。它是在 if 语句中包装表格,如下所示。对于操作表,我发现在 iPad 的滚动视图中使用其他解决方案(比如将每个表格和按钮包装在一个组中)通常会让操作表到奇怪的地方,所以这个答案将解决 iPad 滚动视图中操作表的问题。

struct ContentView: View{
@State var sheet1 = false
@State var sheet2 = false
var body: some View{
VStack{
Button(action: {
self.sheet1.toggle()
},label: {
Text("Sheet 1")
}).padding()
Button(action: {
self.sheet2.toggle()
},label: {
Text("Sheet 2")
}).padding()
}
if self.sheet1{
Text("")
.sheet(isPresented: self.$sheet1, content: {
Text("Some content here presenting sheet 1")
})
}
if self.sheet2{
Text("")
.sheet(isPresented: self.$sheet2, content: {
Text("Some content here presenting sheet 2")
})
}


}
}

UPD

Xcode 12.5.0 Beta 3(2021年3月3日)开始,这个问题就没有意义了,因为现在可能有多个 .sheet(isPresented:).fullScreenCover(isPresented:)在一行中,并且问题中提出的代码将工作得很好。

尽管如此,我发现这个答案仍然有效,因为它很好地组织了表格,使代码更干净,更易读——你有一个真理的来源,而不是一对独立的布尔值

真正的答案

最好的方法,同样适用于 IOS14:

enum ActiveSheet: Identifiable {
case first, second
    

var id: Int {
hashValue
}
}


struct YourView: View {
@State var activeSheet: ActiveSheet?


var body: some View {
VStack {
Button {
activeSheet = .first
} label: {
Text("Activate first sheet")
}


Button {
activeSheet = .second
} label: {
Text("Activate second sheet")
}
}
.sheet(item: $activeSheet) { item in
switch item {
case .first:
FirstView()
case .second:
SecondView()
}
}
}
}

阅读更多: https://developer.apple.com/documentation/swiftui/view/sheet(item:ondismiss:content:)

要隐藏工作表,只需设置 activeSheet = nil

意外收获: 如果您希望您的工作表是全屏的,那么使用完全相同的代码,但是代替 .sheet.fullScreenCover

这个示例显示了在同一 ContentView中使用4个工作表、1个(或更多)警报和一个 actionSheet。好的,在 iOS13,iOS14中。好的,预览中

(来自注释:)这样做的目的是使用 sheet(item:onDismiss:content:),将 item 作为 @State变量,并在枚举中定义值。这样,所有的“业务”都是自包含在 ContentView中的。以这种方式,工作表或警报的数量是不受限制的。

下面是以下代码的输出:

All in one

import SwiftUI


// exemple which show use of 4 sheets,
// 1 (or more) alerts,
// and an actionSheet in the same ContentView
// OK in iOS 13, iOS 14
// OK in Preview


// Any number of sheets, displayed as Views
// can be used for sheets in other views (with unique case values, of course)
enum SheetState {
case none
case AddItem
case PickPhoto
case DocPicker
case ActivityController
}


// Make Identifiable
extension SheetState: Identifiable {
var id: SheetState { self }
}


// the same for Alerts (who are not View, but Alert)
enum AlertState {
case none
case Delete
}


extension AlertState: Identifiable {
var id: AlertState { self }
}


struct ContentView: View {


// Initialized with nil value
@State private var sheetState: SheetState?
@State private var alertState: AlertState?


var body: some View {
NavigationView {
Form {
Text("Hello, world!")
Section(header: Text("sheets")) {
addItemButton
pickDocumentButton
pickPhoto
buttonExportView
}
Section(header: Text("alert")) {
confirmDeleteButton
}
Section(header: Text("Action sheet")) {
showActionSheetButton
}
}
.navigationTitle("Sheets & Alerts")
                    

// ONLY ONE call .sheet(item: ... with required value in enum
// if item become not nil => display sheet
// when dismiss sheet (drag the modal view, or use presentationMode.wrappedValue.dismiss in Buttons) => item = nil
// in other way : if you set item to nil => dismiss sheet
                    

// in closure, look for which item value display which view
// the "item" returned value contains the value passed in .sheet(item: ...
.sheet(item: self.$sheetState) { item in
if item == SheetState.AddItem {
addItemView // SwiftUI view
} else if item == SheetState.DocPicker {
documentPickerView // UIViewControllerRepresentable
} else if item == SheetState.PickPhoto {
imagePickerView // UIViewControllerRepresentable
} else if item == SheetState.ActivityController {
activityControllerView // UIViewControllerRepresentable
}
            

}
        

.alert(item: self.$alertState) { item in
if item == AlertState.Delete {
return deleteAlert
} else {
// Not used, but seem to be required
// .alert(item: ... MUST return an Alert
return noneAlert
}
}
}
}


// For cleaner contents : controls, alerts and sheet views are "stocked" in private var


// MARK: - Sheet Views


private var addItemView: some View {
Text("Add item").font(.largeTitle).foregroundColor(.blue)
// drag the modal view set self.sheetState to nil
}


private var documentPickerView: some View {
DocumentPicker() { url in
if url != nil {
DispatchQueue.main.async {
print("url")
}
}
self.sheetState = nil
// make the documentPicker view dismissed
}
}


private var imagePickerView: some View {
ImagePicker() { image in
if image != nil {
DispatchQueue.main.async {
self.logo = Image(uiImage: image!)
}
}
self.sheetState = nil
}
}


private var activityControllerView: some View {
ActivityViewController(activityItems: ["Message to export"], applicationActivities: [], excludedActivityTypes: [])
}


// MARK: - Alert Views


private var deleteAlert: Alert {
Alert(title: Text("Delete?"),
message: Text("That cant be undone."),
primaryButton: .destructive(Text("Delete"), action: { print("delete!") }),
secondaryButton: .cancel())
}


private var noneAlert: Alert {
Alert(title: Text("None ?"),
message: Text("No action."),
primaryButton: .destructive(Text("OK"), action: { print("none!") }),
secondaryButton: .cancel())
}


// In buttons, action set value in item for .sheet(item: ...
// Set self.sheetState value make sheet displayed
// MARK: - Buttons


private var addItemButton: some View {
Button(action: { self.sheetState = SheetState.AddItem }) {
HStack {
Image(systemName: "plus")
Text("Add an Item")
}
}
}


private var pickDocumentButton: some View {
Button(action: { self.sheetState = SheetState.DocPicker }) {
HStack {
Image(systemName: "doc")
Text("Choose Document")
}
}
}


@State private var logo: Image = Image(systemName: "photo")
private var pickPhoto: some View {
ZStack {
HStack {
Text("Pick Photo ->")
Spacer()
}
HStack {
Spacer()
logo.resizable().scaledToFit().frame(height: 36.0)
Spacer()
}
}
.onTapGesture { self.sheetState = SheetState.PickPhoto }
}


private var buttonExportView: some View {
Button(action: { self.sheetState = SheetState.ActivityController }) {
HStack {
Image(systemName: "square.and.arrow.up").imageScale(.large)
Text("Export")
}
}
}


private var confirmDeleteButton: some View {
Button(action: { self.alertState = AlertState.Delete}) {
HStack {
Image(systemName: "trash")
Text("Delete!")
}.foregroundColor(.red)
}
}


@State private var showingActionSheet = false
@State private var foregroundColor = Color.blue
private var showActionSheetButton: some View {
Button(action: { self.showingActionSheet = true }) {
HStack {
Image(systemName: "line.horizontal.3")
Text("Show Action Sheet")
}.foregroundColor(foregroundColor)
}
.actionSheet(isPresented: $showingActionSheet) {
ActionSheet(title: Text("Change foreground"), message: Text("Select a new color"), buttons: [
.default(Text("Red")) { self.foregroundColor = .red },
.default(Text("Green")) { self.foregroundColor = .green },
.default(Text("Blue")) { self.foregroundColor = .blue },
.cancel()
])
}
}


struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

另一种在一个视图中显示多个工作表的简单方法是:

每个视图 private 变量都有自己的 Bool@State 值和. sheet (isPrested: ... call)

很容易实现,所有必要的都放在一个地方。 好的,在 iOS13,iOS14,预览

import SwiftUI


struct OtherContentView: View {
var body: some View {
Form {
Section {
button1
}
Section {
button2
}
Section {
button3
}
Section {
button4
}
}
}
    

@State private var showSheet1 = false
private var button1: some View {
Text("Sheet 1")
.onTapGesture { showSheet1 = true }
.sheet(isPresented: $showSheet1) { Text("Modal Sheet 1") }
}
    

@State private var showSheet2 = false
private var button2: some View {
Text("Sheet 2")
.onTapGesture { showSheet2 = true }
.sheet(isPresented: $showSheet2) { Text("Modal Sheet 2") }
}
    

@State private var showSheet3 = false
private var button3: some View {
Text("Sheet 3")
.onTapGesture { showSheet3 = true }
.sheet(isPresented: $showSheet3) { Text("Modal Sheet 3") }
}
    

@State private var showSheet4 = false
private var button4: some View {
Text("Sheet 4")
.onTapGesture { showSheet4 = true }
.sheet(isPresented: $showSheet4) { Text("Modal Sheet 4") }
}
}


struct OtherContentView_Previews: PreviewProvider {
static var previews: some View {
OtherContentView()
}
}

我的应用程序在 iOS 13.x 上有三种表格展示方式,这种方式效果很好。有趣的行为始于 iOS14。由于某些原因,在应用程序启动时,当我选择了一个工作表要呈现的状态变量没有得到设置,工作表出现在一个空白屏幕。如果我继续选择第一个选项,它将继续呈现一张空白表。只要我选择第二个选择(不同于第一个)的变量设置和适当的工作表出现。不管我先选择哪一张,相同的行为都会发生。

小虫?还是我错过了什么。我的代码除了3个工作表选项外,几乎和上面的代码一样,并且我有一个自定义按钮,它带有一个参数,()-> Void,当按钮被按下时运行。在 iOS13.x 中工作得很好,但在 iOS14中就不行了。

戴夫

我解决了混乱的 @State和多张通过创建一个可观察的 SheetContext持有和管理的状态为我。然后,我只需要一个上下文实例,并且可以告诉它将任何视图显示为工作表。

我在这篇博客文章中更详细地描述了它: https://danielsaidi.com/blog/2020/06/06/swiftui-sheets

我不认为这是 SwiftUI 呈现任何视图的正确方式。

这个范例的工作原理是创建特定的视图,在屏幕上显示一些内容,因此您可以在 Superview 主体内部拥有多个视图,这些视图需要显示某些内容。因此,iOS 14上的 SwiftUI 2不会接受这一点,开发人员应该调用“超视图”中所有在某些情况下可以接受的演示文稿,但是如果特定视图显示内容,那么会有更好的时刻。

我为此实现了一个解决方案,并在 Swift 5.3上用 iOS 14.1上的 Xcode 12.1进行了测试

struct Presentation<Content>: View where Content: View {
enum Style {
case sheet
case popover
case fullScreenCover
}


@State private var isTrulyPresented: Bool = false
@State private var willPresent: Bool = false
@Binding private var isPresented: Bool


let content: () -> Content
let dismissHandler: (() -> Void)?
let style: Style


init(_ style: Style, _ isPresented: Binding<Bool>, onDismiss: (() -> Void)?, content: @escaping () -> Content) {
self._isPresented = isPresented
self.content = content
self.dismissHandler = onDismiss
self.style = style
}


@ViewBuilder
var body: some View {
if !isPresented && !willPresent {
EmptyView()
} else {
switch style {
case .sheet:
EmptyView()
.sheet(isPresented: $isTrulyPresented, onDismiss: dismissHandler, content: dynamicContent)
case .popover:
EmptyView()
.popover(isPresented: $isTrulyPresented, content: dynamicContent)
case .fullScreenCover:
EmptyView()
.fullScreenCover(isPresented: $isTrulyPresented, onDismiss: dismissHandler, content: dynamicContent)
}
}
}
}


extension Presentation {
var dynamicContent: () -> Content {
if isPresented && !isTrulyPresented {
OperationQueue.main.addOperation {
willPresent = true
OperationQueue.main.addOperation {
isTrulyPresented = true
}
}
} else if isTrulyPresented && !isPresented {
OperationQueue.main.addOperation {
isTrulyPresented = false
OperationQueue.main.addOperation {
willPresent = false
}
}
}


return content
}
}

之后,我可以在 SwiftUI 中为所有视图实现这些方法

public extension View {
func _sheet<Content>(
isPresented: Binding<Bool>,
content: @escaping () -> Content
) -> some View where Content: View {


self.background(
Presentation(
.sheet,
isPresented,
onDismiss: nil,
content: content
)
)
}


func _sheet<Content>(
isPresented: Binding<Bool>,
onDismiss: @escaping () -> Void,
content: @escaping () -> Content
) -> some View where Content: View {


self.background(
Presentation(
.sheet,
isPresented,
onDismiss: onDismiss,
content: content
)
)
}
}


public extension View {
func _popover<Content>(
isPresented: Binding<Bool>,
content: @escaping () -> Content
) -> some View where Content: View {


self.background(
Presentation(
.popover,
isPresented,
onDismiss: nil,
content: content
)
)
}
}


public extension View {
func _fullScreenCover<Content>(
isPresented: Binding<Bool>,
content: @escaping () -> Content
) -> some View where Content: View {


self.background(
Presentation(
.fullScreenCover,
isPresented,
onDismiss: nil,
content: content
)
)
}


func _fullScreenCover<Content>(
isPresented: Binding<Bool>,
onDismiss: @escaping () -> Void,
content: @escaping () -> Content
) -> some View where Content: View {


self.background(
Presentation(
.fullScreenCover,
isPresented,
onDismiss: onDismiss,
content: content
)
)
}
}

这个解决方案适用于 iOS14.0

此解决方案使用 。页(项目: ,内容:) 构造

struct ContentView: View {
enum ActiveSheet: Identifiable {
case First, Second
        

var id: ActiveSheet { self }
}
    

@State private var activeSheet: ActiveSheet?


var body: some View {


NavigationView {
VStack(spacing: 20) {
Button("First modal view") {
activeSheet = .First
}
Button ("Second modal view") {
activeSheet = .Second
}
}
.navigationBarTitle(Text("Multiple modal view problem"), displayMode: .inline)
.sheet(item: $activeSheet) { sheet in
switch sheet {
case .First:
Text("First modal view")
case .Second:
Text("Only the second modal view works!")
}
}
}
}
}

对于这个派对来说有点晚了,但是到目前为止还没有一个答案涉及到使用 viewModel 完成工作的可能性。由于我并不是 SwiftUI 方面的专家(我对 SwiftUI 相当陌生) ,因此完全有可能有更好的方法来实现这一点,但我得出的解决方案是这样的:

enum ActiveSheet: Identifiable {
case first
case second
        

var id: ActiveSheet { self }
}


struct MyView: View {


@ObservedObject private var viewModel: MyViewModel


private var activeSheet: Binding<ActiveSheet?> {
Binding<ActiveSheet?>(
get: { viewModel.activeSheet },
set: { viewModel.activeSheet = $0 }
)
}


init(viewModel: MyViewModel) {
self.viewModel = viewModel
}


var body: some View {


HStack {
/// some views
}
.onTapGesture {
viewModel.doSomething()
}
.sheet(item: activeSheet) { _ in
viewModel.activeSheetView()
}
}
}

... 和在视图模型-

    @Published var activeSheet: ActiveSheet?


func activeSheetView() -> AnyView {
        

switch activeSheet {
case .first:
return AnyView(firstSheetView())
case .second:
return AnyView(secondSheetView())
default:
return AnyView(EmptyView())
}
}


// call this from the view, eg, when the user taps a button
func doSomething() {
activeSheet = .first // this will cause the sheet to be presented
}

其中 firstSheetView ()和 Second SheetView ()正在提供所需的 actionSheet 内容。

我喜欢这种方法,因为它将所有业务逻辑都排除在视图之外。

编辑: 从 IOS 14.5 beta 3开始,这个问题现在已经解决了:

SwiftUI 在 iOS 和 iPadOS 14.5 Beta 3中解决

  • 现在可以在同一视图层次结构中应用多个 sheet(isPresented:onDismiss:content:)fullScreenCover(item:onDismiss:content:)修饰符

在修复之前,一个解决方案是将工作表修饰符应用到每个 Button:

struct ContentView: View {


@State private var firstIsPresented = false
@State private var secondIsPresented = false


var body: some View {


NavigationView {
VStack(spacing: 20) {
Button("First modal view") {
self.firstIsPresented.toggle()
}
.sheet(isPresented: $firstIsPresented) {
Text("First modal view")
}


Button ("Second modal view") {
self.secondIsPresented.toggle()
}
.sheet(isPresented: $secondIsPresented) {
Text("Second modal view")
}
}
.navigationBarTitle(Text("Multiple modal view problem"), displayMode: .inline)
}
}
}

因为这两个工作表都做同样的事情,所以你可以将这些重复的功能提取到一个子视图中:

struct ContentView: View {
var body: some View {
NavigationView {
VStack(spacing: 20) {
ShowSheet(title:"First modal view")
ShowSheet(title:"Second modal view")
}
.navigationBarTitle(Text("Multiple modal view no problem!"), displayMode: .inline)
}
}
}


struct ShowSheet: View {
@State private var isPresented = false
let title: String
var body: some View {
Button(title) {
isPresented.toggle()
}
.sheet(isPresented: $isPresented) {
Text(title)
}
}
}

从 iOS 和 iPadOS 14.5 Beta 3开始,无论何时它们将公开发布,多个工作表将如预期一样工作,并且不需要其他解决方案中的任何变通方法。从发行说明中可以看出:

SwiftUI

在 iOS 和 iPadOS 14.5 Beta 3中解决

现在可以应用多个 sheet(isPresented:onDismiss:content:)和 同一视图中的 fullScreenCover(item:onDismiss:content:)修饰符 (74246633)

接受的解决方案工作得很好,但我想分享一个额外的扩展,以防其他人遇到同样的问题。

我的问题

我遇到了一个问题,两个按钮合二为一。将两个按钮配对在一起,将整个 VStackHStack转换成一个单独的大按钮。这只允许一个 .sheet触发,无论使用接受的。

解决方案

对我来说,回答我就是缺失的一块拼图。

向每个按钮添加 .buttonStyle(BorderlessButtonStyle()).buttonStyle(PlainButtonStyle()),使它们像您期望的那样充当单个按钮。

对不起,如果我在这里添加了这个,我犯了任何错误,但这是我第一次张贴在 StackOverlflow。

除上述答案外

  1. 如果两个工作表具有顺序关系,则可以替换旧工作表
    import SwiftUI
struct Sheet1: View {
@Environment(\.dismiss) private var dismiss
@State var text: String = "Text"
        

var body: some View {
            

Text(self.text)
if self.text == "Modified Text" {
Button {
dismiss()
} label: {
Text("Close sheet")
}
} else {
Button {
self.text = "Modified Text"
} label: {
Text("Modify Text")
}
}
}
}
struct SheetTester: View {
@State private var isShowingSheet1 = false
        

var body: some View {
Button(action: {
isShowingSheet1.toggle()
}) {
Text("Show Sheet1")
}
.sheet(isPresented: $isShowingSheet1) {
Sheet1()
}
}
}

或者2。使用两张平行的纸

    struct SheetTester: View {
@State private var isShowingSheet1 = false
var body: some View {
Button(action: {
isShowingSheet1.toggle()
}) {
Text("Show Sheet1")
}
.sheet(isPresented: $isShowingSheet1) {
Text("Sheet1")
Button {
isShowingSheet1.toggle()
isShowingSheet2.toggle()
} label: {
Text("Show Sheet2")
}
}
.sheet(isPresented: $isShowingSheet2) {
Text("Sheet2")
}
}
}
}

我会诚实的告诉你,我想象这样简单的解决方法。 你把这个工作表放进去,然后在这个工作表里面放一个文本在一些堆栈里面(不需要) ,然后在这个堆栈里面再放一个工作表,然后用第二个布尔值打开另一个。就像矩阵女神一样。