swiftui bug 多

In order for a view to be displayed modally in SwiftUI, the sheet modifier can be used. In its simplest form a sheet is being presented if a given condition is true.

为了在SwiftUI中以模态显示视图,可以使用sheet修饰符。 如果给定条件为真,则以其最简单的形式显示工作表。

struct ContentView: View {@State var presentSheet = falsevar body: some View {
        VStack {
            Button("Show Settings") {
                self.presentSheet = true
            }
        }.sheet(isPresented: $presentSheet) {
            SettingsView()
        }
    }
}

In this example, the sheet modifier is applied to the VStack. However, it could also be applied to the Button directly.

在此示例中,sheet修改器应用于VStack。 但是,它也可以直接应用于Button。

What if we wanted to display more than one sheet from a specific view, though?

但是,如果我们想从一个特定的视图显示多个图纸怎么办?

The most logical choice would be to just place another sheet modifier below the existing one, since it is possible to chain modifiers as we please. Unfortunately this does not work. In order for us to be able to use multiple sheets, each modifier has to be applied to the button directly. Using two or more consecutive sheet modifiers does not work in SwiftUI, only the last sheet modifier will be applied and can be used. As a result, we could design our view like this.

最合乎逻辑的选择是将另一个图纸修改器放置在现有的下面,因为我们可以随意链接修改器。 不幸的是,这不起作用。 为了使我们能够使用多张图纸,必须将每个修饰符直接应用于按钮。 在SwiftUI中,无法使用两个或多个连续的工作表修改器,只有最后一个工作表修改器可以应用并且可以使用。 结果,我们可以这样设计视图。

@State var showSettingsView = false
@State var showProfileView = falsevar body: some View {
    VStack {
        Button("Show Settings") {
            self.showSettingsView = true
        }.sheet(isPresented: $showSettingsView) {
            SettingsView()
        }
        Button("Show Profile") {
            self.showProfileView = true
        }.sheet(isPresented: $showProfileView) {
            ProfileView()
        }
    }
}

But as you can imagine in a working app, this view won’t be as compact. If you need to handle multiple different sheets, the code gets blown up very quickly. Additionally, each sheet needs its own @State to be presented.

但是,正如您在工作的应用程序中可以想象的那样,这种视图不会那么紧凑。 如果您需要处理多个不同的工作表,则代码很快就会崩溃。 此外,每个工作表都需要显示自己的@ 状态 。

To avoid massive Views, it would be great if there was a way to separate this navigation logic from our view into a dedicated file. There are different alternatives that simplify the handling of multiple sheet modifiers. This article shows one of them.

为了避免大量的Views ,如果有一种方法可以将这种导航逻辑从我们的视图中分离到一个专用文件中,那就太好了。 有不同的替代方法可以简化对多个图纸修改器的处理。 本文显示了其中之一。

Moving the presentation logic into an observable ViewModel

将表示逻辑移动到可观察的ViewModel中

First of all we are going to separate the logic of which sheet will be shown to a separate view model.

首先,我们将把显示哪张图纸的逻辑分离到单独的视图模型中。

class SheetNavigator: ObservableObject {
    @Published var showSettingsView = false
    @Published var showProfileView = false
}

This SheetNavigator class has to conform to the ObservableObject protocol and will be observed by the view itself using the @ObservedObject property wrapper.

该SheetNavigator类必须符合ObservableObject协议,并且视图本身将使用@ObservedObject属性包装器对其进行观察。

@ObservedObject var sheetNavigator = SheetNavigator()
var body: some View {
    VStack {
        Button("Show Settings") {
            self.sheetNavigator.showSettingsView = true
        }.sheet(isPresented: self.$sheetNavigator.showSettingsView) {
      SettingsView()
        }
        Button("Show Profile") {
            self.sheetNavigator.showProfileView = true
        }.sheet(isPresented: self.$sheetNavigator.showProfileView) {
            ProfileView()
        }
    }
}

After we moved our presentation state variables into the view model, the code did not really improve. The state handling is not scalable and we still have multiple sheet modifiers cluttering our view. Let‘s reduce those at first to just one sheet modifier.

在将表示状态变量移动到视图模型之后,代码并没有真正改善。 状态处理不可扩展,并且我们仍然有多个工作表修饰符使视图混乱。 首先让我们将其减少为一个工作表修改器。

VStack {
    Button("Show Settings") {
        self.sheetNavigator.showSettingsView = true
    }
    Button("Show Profile") {
        self.sheetNavigator.showProfileView = true
    }
}.sheet(isPresented: ???) {
    ???
}

At this point we only have one sheet modifier, but which view should be shown and to which state variable should we bind the sheet? The answer is our SheetNavigator.

此时,我们只有一个工作表修改器,但是应该显示哪个视图以及将工作表绑定到哪个状态变量? 答案是我们的SheetNavigator。

class SheetNavigator: ObservableObject {
    var showSettingsView = false
    var showProfileView = false
    @Published var showSheet = false

    func sheetView() -> AnyView {
        if showFirstView {
            return Text("SettingsView").eraseToAnyView()
        } else if showSecondView {
            return Text("ProfileView").eraseToAnyView()
        }
        return Text("Empty").eraseToAnyView()        
    }
}extension View {
    func eraseToAnyView() -> AnyView {
        AnyView(self)
    }
}

We add an additional bool state to handle the binding for all sheets. The function sheetView returns the View we want to show for the selected button. All this logic happens in the navigator and our view is way simpler than before.

我们添加了一个额外的布尔状态来处理所有工作表的绑定。 函数sheetView返回我们要为所选按钮显示的视图。 所有这些逻辑都发生在导航器中,并且我们的视图比以前更简单。

Since we are dealing with different views that are displayed in our sheets, we have to type erase said views to AnyView. For the sake of readability, we added a little View extension to simplify wrapping a view inside of AnyView.

由于我们要处理工作表中显示的不同视图,因此必须将“擦除”视图键入AnyView。 为了便于阅读,我们添加了一个小的View扩展名以简化将视图包装到AnyView中的过程。

As there might be performance implications when using AnyView, you could also consider wrapping your views inside a Group.

由于使用AnyView可能会影响性能,因此您也可以考虑将视图包装在Group中 。

VStack {
    Button("Show Settings") {
        self.sheetNavigator.showSettingsView = true
        self.sheetNavigator.showSheet = true
    }
    Button("Show Profile") {
        self.sheetNavigator.showProfileView = true
        self.sheetNavigator.showSheet = true
    }    
}.sheet(isPresented: self.$sheetNavigator.showSheet) {
    self.sheetNavigator.sheetView()
}

But it’s not perfect, since we need to set two different booleans in the action of a button, one for the sheet that should be shown and one for the state that is bound to the sheet modifier. Additionally, the amount of state variables required, will grow for each new sheet we want to show. Let‘s use a different approach, that is more scalable and also improves readability: Enums.

但这不是完美的,因为我们需要在按钮的动作中设置两个不同的布尔值,一个用于应该显示的工作表,另一个用于绑定到工作表修饰符的状态。 此外,对于每个我们要显示的新工作表,所需的状态变量的数量都会增加。 让我们使用另一种方法,它具有更高的可扩展性并提高了可读性:枚举。

Enums to the rescue!

列举救援!

Now there is only one published parameter left, the showSheet state that is bound to the sheet modifier. For the different sheet views we want to present, we added a SheetDestination enum.

现在只剩下一个发布的参数,即绑定到sheet修改器的showSheet状态。 对于我们要呈现的不同工作表视图,我们添加了一个SheetDestination枚举。

class SheetNavigator: ObservableObject {@Published var showSheet = false
    var sheetDestination: SheetDestination = .none

    enum SheetDestination {
        case none
        case settings
        case profile
    }

  func sheetView() -> AnyView {
      switch sheetDestination {
      case .none:
          return Text("None").eraseToAnyView()
      case .settings:
          return SettingsView().eraseToAnyView()
      case .profile:
          return ProfileView().eraseToAnyView()
      }
  }
}

With these changes the View will now look like this.

通过这些更改,视图现在将如下所示。

@ObservedObject var sheetNavigator = SheetNavigator()
var body: some View {
    VStack {
        Button("Show Settings") {
            self.sheetNavigator.sheetDestination = .settings
            self.sheetNavigator.showSheet = true
        }
        Button("Show Profile") {
            self.sheetNavigator.sheetDestination = .profile
            self.sheetNavigator.showSheet = true
        }
    }.sheet(isPresented: self.$sheetNavigator.showSheet) {
        self.sheetNavigator.sheetView()
    }
}

At this point, we are still calling showSheet ourselves. Actually, setting the sheetDestination should really be enough. So we remove setting showSheet to true from the view and handle this with a property observer.

此时,我们仍将自己称为showSheet 。 实际上, 只需设置sheetDestination就足够了。 因此,我们从视图中删除将showSheet设置为true,并使用属性观察器进行处理。

var sheetDestination: SheetDestination = .none {
    didSet {
        showSheet = true
    }
}

Every time the sheetDestination is being set, we set showModal to true.

每次设置sheetDestination时 ,我们都将showModal设置为true。

VStack {
    Button("Show Settings") {
        self.sheetNavigator.sheetDestination = .settings
    }
    Button("Show Profile") {
        self.sheetNavigator.sheetDestination = .profile
    }
}.sheet(isPresented: self.$sheetNavigator.showSheet) {
    self.sheetNavigator.sheetView()
}

What‘s next?

下一步是什么?

In using our navigator, we managed to condense our view and moved most of our navigation logic out of it. But there is still room for improvement. We can take advantage of the fact that Swift enums support associated values and pass additional data to the presented sheet views.

在使用导航器时,我们设法压缩了视图,并将大部分导航逻辑移出了视图。 但是仍有改进的空间。 我们可以利用Swift枚举支持关联值并将附加数据传递到所呈现的图纸视图这一事实。

For instance, we can make the profile enum case accept a String as parameter, if we want to pass the name of the user to the profile view.

例如,如果要将用户名传递给配置文件视图,我们可以使配置文件枚举大小写接受String作为参数。

enum SheetDestination {
    case none
    case settings
    case profile(name: String)
}

When setting the sheetDestination to the profile case, our call site would look like this.

当将sheetDestination设置为配置文件大小写时,我们的呼叫站点将如下所示。

Button("Show Profile") {
   self.sheetNavigator.sheetDestination = .profile(name: "Christoph")
}

The profile case of the switch statement within the sheetView function, has to be adjusted as well. Here, we can use the given parameter and pass it to the ProfileView.

sheetView函数中switch语句的配置文件大小写也必须进行调整。 在这里,我们可以使用给定的参数并将其传递给ProfileView。

case .profile(name: let userName):
         return ProfileView(name: userName).eraseToAnyView()

Keep in mind to avoid too many destinations in one Navigator class. In fact, each view you are using should have its own Navigator.

请记住,避免在一个Navigator类中出现太多目的地。 实际上,您正在使用的每个视图都应具有自己的导航器。

To establish a consistent style, you could use a Navigator Protocol each Navigator can confirm to.

要建立一致的样式,可以使用每个导航器可以确认的导航器协议。

protocol NavigatorProtocol: ObservableObject {
    associatedtype SheetDestination
    func sheetView() -> AnyView
}

I hope you enjoyed this little concept of handling multiple sheets. How do you handle navigating to different views in SwiftUI? Do you have similar approaches? Let us know! We‘d love to hear your ideas and hope this article helped to StepUp your developer game.

我希望您喜欢处理多张纸的小概念。 如何处理在SwiftUI中导航到不同的视图? 您有类似的方法吗? 让我们知道! 我们希望听到您的想法,希望本文对StepUp开发者游戏有所帮助。

翻译自: https://medium.com/stepup-development/handling-multiple-sheets-in-swiftui-2e9a73d99cd7

swiftui bug 多