SwiftUI MVVM 协调器/路由器/导航链接 [英] SwiftUI MVVM Coordinator/Router/NavigationLink

查看:12
本文介绍了SwiftUI MVVM 协调器/路由器/导航链接的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在将 UIKit 架构模式转换为 SwiftUI 时遇到问题.我目前的模式主要是带有协调器/路由器的 MVVM.添加@ObservableObject/@Published 后,MVVM 部分看起来非常简单和自然.但是协调/路由似乎不直观.视图和协调(导航)功能在 SwiftUI 中紧密耦合.除了使用辅助结构 AnyView 之外,似乎不太可能将它们分开.

I am having problems to translate UIKit architecture patterns to SwiftUI. My current pattern is mostly MVVM with Coordinators/Routers. The MVVM part seems quite easy and natural with the addition of @ObservableObject/@Published. But the coordinating/routing seems unintuitive. The View and the coordination (navigation) functionality are tightly coupled in SwiftUI. It seems like it's not really possible to separate them apart from using the helper struct AnyView.

这是一个例子:我想在 SwiftUI 中创建一个可重用的行/单元格.假设生产中的这一行非常复杂,因此我想重用它.我也想把它放在另一个模块中,这样我就可以在多个目标中重用它.(例如 iOS、macCatalyst 等...)

Here one example: I want to create a reusable row/cell in SwiftUI. Let say that this row in Production is quite complex therefore I want to reuse it. I want to place it also in another module so I can reuse it in multiple targets. (like iOS, macCatalyst, etc...)

现在我想控制当用户点击该视图或该视图中的按钮时会发生什么.根据上下文,我需要导航到不同的目的地.据我所知,可能的 NavigationLink 目标必须硬连线到视图中,或者 AnyView 必须传递到视图中.

Now I want to control what happens when the user taps on that view or buttons in that view. Depending on the context I need to navigate to different destinations. As far I can see the possible NavigationLink targets have to be either hardwired into the view or AnyView has to be passed into the View.

这里有一些示例代码.此单元格/行包含两个按钮.我想导航到其他一些取决于上下文的视图,而不是硬连接到代码中:

Here some sample code. This cell/row contains two buttons. I want to navigate to some other view which is dependent on the context and not to be hardwired into the code:

struct ProductFamilyRow: View {
    @State private var selection: Int? = 0
    let item: ProductFamilyItem

    let destinationView1: AnyView
    let destinationView2: AnyView

    var body: some View {
        VStack {
            NavigationLink(
                destination: destinationView1,
                tag: 1,
                selection: self.$selection
            ) {
                EmptyView()
            }

            NavigationLink(
                destination: destinationView2,
                tag: 2,
                selection: self.$selection
            ) {
                EmptyView()
            }

            HStack {
                Text(item.title)
                Button("Destination 1") {
                    self.selection = 1
                }.foregroundColor(Color.blue)

                Button("Destination 2") {
                    self.selection = 2
                }.foregroundColor(Color.blue)
            }

            //Image(item.image)
        }.buttonStyle(PlainButtonStyle())
    }
}

这似乎是 SwiftUI 的一个主要设计缺陷.除了使用 AnyView hack 之外,带有导航链接的可重用组件基本上是不可能的.据我所知 AnyView 仅用于我需要类型擦除并且有很多性能缺陷的特定用例.所以我不认为这是使用 SwiftUI 创建可重用、可导航的视图的惯用解决方案.

This seems to be a major design flaw in SwiftUI. Reusable components with Navigation Links are basically not possible apart from using the AnyView hack. As far as I know AnyView is just used for specific use cases where I need type-erasure and has quite some performance drawbacks. So I do not consider this the idiomatic solution to create reusable, navigatable views with SwiftUI.

这真的是唯一的解决方案吗?也许我完全错了,无论如何这是错误的方向.我在某处读到(再也找不到帖子了..)关于使用一些指示要显示哪个视图的中央状态,但我没有看到具体的例子如何做到这一点.

Is this really the only solution? Maybe I am totally wrong and this is anyway the wrong direction. I read somewhere (can't find the post anymore..) about using some central state which indicates which view to show but I saw no concrete example how to do this.

第二个挑战:此外,我不希望单元格对任何其他点击然后在按钮上做出反应.但是似乎无法控制单元格在点击时导航到的位置.(所以不要点击其中一个按钮,而是点击单元格中的任何地方)在当前示例代码中,它(出于任何原因)导航到目的地 2".

2nd challenge: Also I do not want the cell to react on any other taps then on the buttons. But it seems not to be possible to control where the cell Navigates to if tapped. (so not tapping on one of the buttons but anywhere in the cell) In the current sample code it navigates (for any reason) to "Destination 2".

提前致谢.

推荐答案

最好为您的行使用泛型,如下(使用 Xcode 11.4 测试)

It is better to use generics for your row, as below (tested with Xcode 11.4)

使用示例:

ProductFamilyRow(item: ProductFamilyItem(title: "Test"),
    destinationView1: { Text("Details1") },
    destinationView2: { Text("Details2") })

界面:

更新 - 添加了行突出显示的块.列表对行内的按钮或链接具有自动检测功能,并在存在任何标准 (!key) 时突出显示.因此,要禁用此类行为,它需要在自定义按钮样式下隐藏所有内容.

Update - added block for row highlight. List has auto detection for button or link inside row and highlights if any standard (!key) present. So, to disable such behaviour it needs to hide everything under custom button style.

struct ProductFamilyRowStyle: ButtonStyle {

    func makeBody(configuration: Self.Configuration) -> some View {
        configuration.label
            .colorMultiply(configuration.isPressed ? 
                 Color.white.opacity(0.5) : Color.white) // any effect you want
    }
}

struct ProductFamilyRow<D1: View, D2: View>: View {
    let item: ProductFamilyItem
    let destinationView1: () -> D1
    let destinationView2: () -> D2

    init(item: ProductFamilyItem, @ViewBuilder destinationView1: @escaping () -> D1,
        @ViewBuilder destinationView2: @escaping () -> D2)
    {
        self.item = item
        self.destinationView1 = destinationView1
        self.destinationView2 = destinationView2
    }

    @State private var selection: Int? = 0

    var body: some View {
        VStack {
            HStack {
                Text(item.title)
                Button(action: {
                    self.selection = 1
                }) {
                    Text("Destination 1")
                        .background( // hide link inside button !!
                            NavigationLink(destination: destinationView1(),
                                tag: 1, selection: self.$selection) { EmptyView() }
                        )
                }.foregroundColor(Color.blue)

                Button(action: {
                    self.selection = 2
                }) {
                    Text("Destination 2")
                        .background(
                            NavigationLink(destination: destinationView2(),
                                tag: 2, selection: self.$selection) { EmptyView() }
                        )
                }.foregroundColor(Color.blue)
            }

            //Image(item.image)
        }.frame(maxWidth: .infinity) // to have container centered
        .buttonStyle(ProductFamilyRowStyle())
    }
}

这篇关于SwiftUI MVVM 协调器/路由器/导航链接的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆