SwiftUI MVVM协调器/路由器/NavigationLink [英] SwiftUI MVVM Coordinator/Router/NavigationLink

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

问题描述

我在将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") })

接口:

更新-为行突出显示添加了块.列表会自动检测行内的按钮或链接,并突出显示是否存在任何标准(!键).因此,要禁用这种行为,需要将所有内容隐藏在自定义按钮样式下.

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协调器/路由器/NavigationLink的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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