SwiftUI - 如何避免导航硬编码到视图中? [英] SwiftUI - how to avoid navigation hardcoded into the view?

查看:20
本文介绍了SwiftUI - 如何避免导航硬编码到视图中?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我尝试为更大的、可用于生产的 SwiftUI 应用程序构建架构.我一直遇到同样的问题,这表明 SwiftUI 存在一个主要的设计缺陷.

I try to do the architecture for a bigger, production ready SwiftUI App. I am running all the time into the same problem which points to a major design flaw in SwiftUI.

仍然没有人能给我一个完整的工作、生产就绪的答案.

Still nobody could give me a full working, production ready answer.

如何在包含导航的 SwiftUI 中实现可重用的视图?

How to do reusable Views in SwiftUI which contain navigation?

由于 SwiftUI NavigationLink 与视图有很强的绑定性,因此它在更大的应用程序中也无法扩展.NavigationLink 在那些小示例应用程序中可以工作,是的 - 但如果您想在一个应用程序中重用许多视图,则不会.也可能在模块边界上重用.(例如:在 iOS、WatchOS 等中重用 View...)

As the SwiftUI NavigationLink is strongly bound to the view this is simply not possible in such a way that it scales also in bigger Apps. NavigationLink in those small sample Apps works, yes - but not as soon as you want to reuse many Views in one App. And maybe also reuse over module boundaries. (like: reusing View in iOS, WatchOS, etc...)

设计问题:导航链接被硬编码到视图中.

The design problem: NavigationLinks are hardcoded into the View.

NavigationLink(destination: MyCustomView(item: item))

但是如果包含这个 NavigationLink 的视图应该是可重用的我不能硬编码目的地.必须有一种机制来提供目的地.我在这里问了这个问题并得到了很好的答案,但仍然不是完整的答案:

But if the view containing this NavigationLink should be reusable I can not hardcode the destination. There has to be a mechanism which provides the destination. I asked this here and got quite a good answer, but still not the full answer:

SwiftUI MVVM 协调器/路由器/NavigationLink

这个想法是将目标链接注入到可重用的视图中.一般来说,这个想法可行,但不幸的是,这不能扩展到真正的生产应用程序.一旦我有多个可重用的屏幕,我就会遇到一个逻辑问题,即一个可重用的视图 (ViewA) 需要一个预配置的视图目标 (ViewB).但是如果 ViewB 还需要一个预配置的视图目标 ViewC 呢?在我注入 ViewB 之前,我需要以这样的方式创建 ViewB进入ViewA.等等......但由于当时必须传递的数据不可用,整个构造失败.

The idea was to inject the Destination Links into the reusable view. Generally the idea works but unfortunately this does not scale to real Production Apps. As soon as I have multiple reusable screens I run into the logical problem that one reusable view (ViewA) needs a preconfigured view-destination (ViewB). But what if ViewB also needs a preconfigured view-destination ViewC? I would need to create ViewB already in such a way that ViewC is injected already in ViewB before I inject ViewB into ViewA. And so on.... but as the data which at that time has to be passed is not available the whole construct fails.

我的另一个想法是使用 Environment 作为依赖注入机制来为 NavigationLink 注入目的地.但我认为这或多或少应该被视为一种黑客行为,而不是大型应用程序的可扩展解决方案.我们最终会基本上将环境用于所有事情.但是因为 Environment 也可以在 View 内部使用(而不是在单独的 Coordinator 或 ViewModel 中),所以在我看来这将再次创建奇怪的构造.

Another idea I had was to use the Environment as dependency injection mechanism to inject destinations for NavigationLink. But I think this should be considered more or less as a hack and not a scalable solution for large Apps. We would end up using the Environment basically for everything. But because Environment also can be used only inside View's (not in separate Coordinators or ViewModels) this would again create strange constructs in my opinion.

就像业务逻辑(例如视图模型代码)和视图必须分开,导航和视图也必须分开(例如协调器模式)在UIKit中这是可能的,因为我们访问了UIViewControllerUINavigationController 在视图后面.UIKit 的 MVC 已经有一个问题,它混合了很多概念,以至于它变成了有趣的名字Massive-View-Controller";而不是模型-视图-控制器".现在 SwiftUI 中仍然存在类似的问题,但在我看来更糟.Navigation 和 Views 是强耦合的,不能解耦.因此,如果它们包含导航,则不可能进行可重用的视图.可以在 UIKit 中解决这个问题,但现在我在 SwiftUI 中看不到一个合理的解决方案.不幸的是,Apple 没有向我们解释如何解决这样的架构问题.我们只有一些小样本应用.

Like business logic (e.g. view model code) and view have to be separated also navigation and view have to be separated (e.g. the Coordinator pattern) In UIKit it's possible because we access to UIViewController and UINavigationController behind the view. UIKit's MVC already had the problem that it mashed up so many concepts that it become the fun-name "Massive-View-Controller" instead of "Model-View-Controller". Now a similar problem continues in SwiftUI but even worse in my opinion. Navigation and Views are strongly coupled and can not be decoupled. Therefore it's not possible to do reusable views if they contain navigation. It was possible to solve this in UIKit but now I can't see a sane solution in SwiftUI. Unfortunately Apple did not provide us an explanation how to solve architectural issues like that. We got just some small sample Apps.

我很想被证明是错误的.请向我展示一个干净的应用设计模式,它可以为大型生产就绪应用解决这个问题.

I would love to be proven wrong. Please show me a clean App design pattern which solves this for big production ready Apps.

提前致谢.

更新:此赏金将在几分钟后结束,不幸的是仍然没有人能够提供一个有效的例子.但是,如果我找不到任何其他解决方案并将其链接到此处,我将开始一个新的赏金来解决这个问题.感谢所有人的伟大贡献!

Update: this bounty will end in a few minutes and unfortunately still nobody was able to provide a working example. But I will start a new bounty to solve this problem if I can't find any other solution and link it here. Thanks to all for their great Contribution!

2020 年 6 月 18 日更新:我从苹果那里得到了关于这个问题的答案,提出了这样的东西来解耦视图和模型:

Update 18th June 2020: I got an answer from Apple regarding this issue, proposing something like this to decouple views and models:

enum Destination {
  case viewA
  case viewB 
  case viewC
}

struct Thing: Identifiable {
  var title: String
  var destination: Destination
  // … other stuff omitted …
}

struct ContentView {
  var things: [Thing]

  var body: some View {
    List(things) {
      NavigationLink($0.title, destination: destination(for: $0))
    }
  }

  @ViewBuilder
  func destination(for thing: Thing) -> some View {
    switch thing.destination {
      case .viewA:
        return ViewA(thing)
      case .viewB:
        return ViewB(thing)
      case .viewC:
        return ViewC(thing)
    }
  }
}

我的回答是:

感谢您的反馈.但正如你所见,你仍然拥有强大的力量视图中的耦合.现在内容视图"需要知道所有的意见(ViewA, ViewB, ViewC) 它也可以导航.正如我所说,这适用于小型示例应用,但无法扩展到大型生产就绪应用.

Thanks for the feedback. But as you see you still have the strong coupling in the View. Now "ContentView" needs to know all the views (ViewA, ViewB, ViewC) it can navigate too. As I said, this works in small sample Apps, but it does not scale to big production ready Apps.

想象一下,我在 GitHub 的一个项目中创建了一个自定义视图.进而在我的应用程序中导入此视图.这个自定义视图什么都不知道关于它也可以导航的其他视图,因为它们是特定的到我的应用程序.

Imagine that I create a custom View in a Project in GitHub. And then import this view in my App. This custom View does not know anything about the other views it can navigate too, because they are specific to my App.

我希望我能更好地解释这个问题.

I hope I explained the problem better.

我看到这个问题的唯一干净的解决方案是分离UIKit 中的导航和视图.(例如 UINavigationController)

The only clean solution I see to this problem is to separate Navigation and Views like in UIKit. (e.g. UINavigationController)

谢谢,达科

所以仍然没有清洁 &这个问题的工作解决方案.期待 WWDC 2020.

So still no clean & working solution for this problem. Looking forward to WWDC 2020.

2021 年 9 月更新:对于这个问题,使用 AnyView 并不是一个好的通用解决方案.在大型应用程序中,基本上所有视图都必须以可重用的方式设计.这意味着 AnyView 可以在任何地方使用.我与两位 Apple 开发人员进行了一次会谈,他们向我清楚地解释了 AnyView 的性能比 View 差得多,而且它应该只在特殊情况下使用.其根本原因是 AnyView 的类型在编译时无法解析,因此必须在堆上分配.

Update September 2021: Using AnyView is not a good general solution for this problem. In big Apps basically all views have to be designed in a reusable way. This would mean that AnyView get's used everywhere. I had a session with two Apple developers and they clearly explained to me the AnyView creates a way worse performance then View and it should be only used in exceptional cases. The underlying reason for this is that the type of AnyView can't be resolved during compile time so it has to be allocated on the heap.

推荐答案

闭包就是你所需要的!

struct ItemsView<Destination: View>: View {
    let items: [Item]
    let buildDestination: (Item) -> Destination

    var body: some View {
        NavigationView {
            List(items) { item in
                NavigationLink(destination: self.buildDestination(item)) {
                    Text(item.id.uuidString)
                }
            }
        }
    }
}

我写了一篇关于用闭包替换 SwiftUI 中的委托模式的文章.https://swiftwithmajid.com/2019/11/06/the-power-of-closures-in-swiftui/

I wrote a post about replacing the delegate pattern in SwiftUI with closures. https://swiftwithmajid.com/2019/11/06/the-power-of-closures-in-swiftui/

这篇关于SwiftUI - 如何避免导航硬编码到视图中?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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