美文网首页
SwiftUI框架详细解析 (十六) —— 基于SwiftUI简

SwiftUI框架详细解析 (十六) —— 基于SwiftUI简

作者: 刀客传奇 | 来源:发表于2021-01-02 11:38 被阅读0次

    版本记录

    版本号 时间
    V1.0 2021.01.02 星期六

    前言

    今天翻阅苹果的API文档,发现多了一个框架SwiftUI,这里我们就一起来看一下这个框架。感兴趣的看下面几篇文章。
    1. SwiftUI框架详细解析 (一) —— 基本概览(一)
    2. SwiftUI框架详细解析 (二) —— 基于SwiftUI的闪屏页的创建(一)
    3. SwiftUI框架详细解析 (三) —— 基于SwiftUI的闪屏页的创建(二)
    4. SwiftUI框架详细解析 (四) —— 使用SwiftUI进行苹果登录(一)
    5. SwiftUI框架详细解析 (五) —— 使用SwiftUI进行苹果登录(二)
    6. SwiftUI框架详细解析 (六) —— 基于SwiftUI的导航的实现(一)
    7. SwiftUI框架详细解析 (七) —— 基于SwiftUI的导航的实现(二)
    8. SwiftUI框架详细解析 (八) —— 基于SwiftUI的动画的实现(一)
    9. SwiftUI框架详细解析 (九) —— 基于SwiftUI的动画的实现(二)
    10. SwiftUI框架详细解析 (十) —— 基于SwiftUI构建各种自定义图表(一)
    11. SwiftUI框架详细解析 (十一) —— 基于SwiftUI构建各种自定义图表(二)
    12. SwiftUI框架详细解析 (十二) —— 基于SwiftUI创建Mind-Map UI(一)
    13. SwiftUI框架详细解析 (十三) —— 基于SwiftUI创建Mind-Map UI(二)
    14. SwiftUI框架详细解析 (十四) —— 基于Firebase Cloud Firestore的SwiftUI iOS程序的持久性添加(一)
    15. SwiftUI框架详细解析 (十五) —— 基于Firebase Cloud Firestore的SwiftUI iOS程序的持久性添加(二)

    开始

    首先看下主要内容:

    在本教程中,您将在SwiftUI中创建社交媒体应用的个人资料页面时,学习有关iOS的Dependency Injection的信息。内容来自翻译

    接着看下写作环境

    Swift 5, iOS 14, Xcode 12

    接着就是正文啦。

    程序员开发了许多体系结构,设计模式和编程风格。 虽然它们各自解决不同的问题,但它们都有助于使代码更具可读性,可测试性和灵活性。

    Inversion of Control 因其效率而广受欢迎。 在本教程中,您将通过Dependency Injection, or DI模式应用此原理。 您无需编写第三方框架,而是编写自己的小型Dependency Injection解决方案,并使用它来重构应用程序并添加一些新功能。

    如果您不知道IoCDI的全部含义,那么没问题,您将很快了解更多。

    注意:这是涉及SwiftUI的中级iOS教程。 如果您不熟悉SwiftUI,请查看SwiftUI视频课程SwiftUI video course

    打开入门项目。 打开入门项目并运行该应用程序:

    您会从社交媒体应用程序中看到一个配置文件屏幕,其中包含许多用户数据:个人简历,朋友,照片和信息。 与任何社交网络一样,用户隐私和互联网安全至关重要。

    您的目标是让用户控制他们与其他用户共享的信息。 另外,您还可以让他们根据他们与给定用户的关系来调整隐私规则。

    在使他们能够控制并了解有关依赖注入(Dependency Injection)及其如何帮助您的更多信息之前,您需要确定问题。

    1. Identifying the Issue

    打开ProfileView.swift并仔细查看ProfileViewbody

    var body: some View {
      NavigationView {
        ScrollView(.vertical, showsIndicators: true) {
          VStack {
            // 1
            ProfileHeaderView(
              user: user,
              canSendMessage: privacyLevel == .friend,
              canStartVideoChat: privacyLevel == .friend
            )
            // 2
            if privacyLevel == .friend {
              UsersView(title: "Friends", users: user.friends)
              PhotosView(photos: user.photos)
              HistoryFeedView(posts: user.historyFeed)
            } else {
              // 3
              RestrictedAccessView()
            }
          }
        }.navigationTitle("Profile")
      }
    }
    

    以下是代码细分:

    • 1) 您将ProfileHeaderView添加到VStack的顶部,并指定仅当用户是朋友时,消息和视频通话选项才可用。
    • 2) 如果用户是朋友,则显示朋友列表,照片和帖子。
    • 3) 否则,您将显示RestrictedAccessView

    ProfileView顶部的privacyLevel值定义查看您的个人资料的用户的访问级别。 将privacyLevel更改为.everyone并运行该应用程序以查看您的个人资料,就好像您不在好友列表中一样:

    已经有基本的隐私控制措施。 但是,用户无法选择谁可以看到其个人资料的哪些部分。 仅有两个隐私级别是不够的。

    当前,ProfileView根据隐私级别决定显示哪些视图。 由于以下几个原因,这不是一个合适的解决方案:

    • 它不是很容易测试。 尽管可以用UI测试进行覆盖,但运行起来要比单元测试或集成测试昂贵。
    • 每次您决定扩展或修改应用程序的功能时,ProfileView都需要进行大量调整。 它与PrivacyLevel紧密结合,并承担了比所需更多的责任。
    • 随着应用程序的复杂性和功能的增长,维护此代码将变得更加困难。

    但是,您可以改善这种情况并使用Dependency Injection无缝添加新功能。


    What Are Inversion of Control and Dependency Injection?

    Inversion of Control是一种模式,可让您反转控制流程。为此,您将一个类的所有职责(主要职责除外)移到了外部,使其成为从属对象(dependencies)。通过抽象,您可以轻松地使依赖项互换。

    您的类(DI客户对象)不知道其依赖项(即DI服务对象)的实现。它还不知道如何创建它们。通过消除类之间的紧密耦合关系,这使得代码可测试和可维护。

    Dependency Injection是帮助应用Inversion of Control原理的几种模式之一。您可以通过多种方式实现依赖项注入(Dependency Injection),包括Constructor Injection, Setter Injection and Interface Injection

    一种常见的方法称为构造函数注入(Constructor Injection)。这是您要看的第一个。

    1. Constructor Injection

    Constructor InjectionInitializer Injection中,您将所有类依赖项作为构造函数参数传递。更容易理解代码的作用,因为您可以在一处立即看到类需要的所有依赖项。例如,查看以下代码片段:

    protocol EngineProtocol {
      func start()
      func stop()
    }
    
    protocol TransmissionProtocol {
      func changeGear(gear: Gear)
    }
    
    final class Car {
      private let engine: EngineProtocol
      private let transmission: TransmissionProtocol
    
      init(engine: EngineProtocol, transmission: TransmissionProtocol) {
        self.engine = engine
        self.transmission = transmission
      }
    }
    

    在此代码段中,EngineProtocolTransmissionProtocol是服务,而Car是客户端。 由于您划分了职责并使用了抽象,因此可以创建一个Car实例,该实例具有符合预期协议的任何依赖关系。 您甚至可以通过EngineProtocolTransmissionProtocol的测试实现,以对Car进行一些单元测试。

    接下来,您将看到Setter Injection

    2. Setter Injection

    Setter InjectionMethod Injection明显不同。 如本例所示,它需要依赖项setter方法:

    final class Car {
      private var engine: EngineProtocol?
      private var transmission: TransmissionProtocol?
    
      func setEngine(engine: EngineProtocol) {
        self.engine = engine
      }
    
      func setTransmission(transmission: TransmissionProtocol) {
        self.transmission = transmission
      }
    }
    

    当您只有几个依赖项并且一些是可选的时,这是一个好方法。 但是,很容易忘记设置必要的依赖项,因为没有什么可以强迫您这样做。

    接下来,您将探索Interface Injection

    3. Interface Injection

    Interface Injection要求客户端遵守用于inject dependencies的协议。 看这个例子:

    protocol EngineMountable {
      func mountEngine(engine: EngineProtocol)
    }
    
    protocol TransmissionMountable {
      func mountTransmission(transmission: TransmissionProtocol)
    }
    
    final class Car: EngineMountable, TransmissionMountable {
      private var engine: EngineProtocol?
      private var transmission: TransmissionProtocol?
    
      func mountEngine(engine: EngineProtocol) {
        self.engine = engine
      }
    
      func mountTransmission(transmission: TransmissionProtocol) {
        self.transmission = transmission
      }
    }
    

    您的代码更加分离。 此外,injector可能完全不了解客户的实际执行情况。

    Dependency Injection ContainerDI Container是另一个重要的Dependency Injection概念。 DI Container负责注册registering和解决resolving项目中的所有依赖项。 根据DI容器的复杂程度,它可以处理依赖项的生命周期,并在必要时自行自动注入依赖项。

    在下一部分中,您将创建一个基本的DI Container


    Using Dependency Injection

    最后,是时候运用您对模式的知识了! 使用以下命令创建一个名为ProfileContentProvider的新Swift文件:

    import SwiftUI
    
    protocol ProfileContentProviderProtocol {
      var privacyLevel: PrivacyLevel { get }
      var canSendMessage: Bool { get }
      var canStartVideoChat: Bool { get }
      var photosView: AnyView { get }
      var feedView: AnyView { get }
      var friendsView: AnyView { get }
    }
    

    尽管此代码只是一个协议,但实现方式决定了要提供哪种内容。

    接下来,在您添加的协议下方添加以下类:

    final class ProfileContentProvider: ProfileContentProviderProtocol {
      let privacyLevel: PrivacyLevel
      private let user: User
    
      init(privacyLevel: PrivacyLevel, user: User) {
        self.privacyLevel = privacyLevel
        self.user = user
      }
    
      var canSendMessage: Bool {
        privacyLevel > .everyone
      }
    
      var canStartVideoChat: Bool {
        privacyLevel > .everyone
      }
    
      var photosView: AnyView {
        privacyLevel > .everyone ? 
          AnyView(PhotosView(photos: user.photos)) : 
          AnyView(EmptyView())
      }
    
      var feedView: AnyView {
        privacyLevel > .everyone ? 
          AnyView(HistoryFeedView(posts: user.historyFeed)) : 
          AnyView(RestrictedAccessView())
      }
    
      var friendsView: AnyView {
        privacyLevel > .everyone ? 
          AnyView(UsersView(title: "Friends", users: user.friends)) : 
          AnyView(EmptyView())
      }
    }
    

    现在,您有一个单独的提供者,其职责是:根据隐私级别决定如何显示用户个人资料。

    接下来,切换到ProfileView.swift并在ProfileViewbody属性上方添加以下代码:

    private let provider: ProfileContentProviderProtocol
    
    init(provider: ProfileContentProviderProtocol, user: User) {
      self.provider = provider
      self.user = user
    }
    

    您在其初始值中设置ProfileViewuser变量,因此删除Mock.user()值分配。

    现在,如下更新ProfileViewbody属性:

    var body: some View {
      NavigationView {
        ScrollView(.vertical, showsIndicators: true) {
          VStack {
            ProfileHeaderView(
              user: user,
              canSendMessage: provider.canSendMessage,
              canStartVideoChat: provider.canStartVideoChat
            )
            provider.friendsView
            provider.photosView
            provider.feedView
          }
        }.navigationTitle("Profile")
      }
    }
    

    有了这些更改,ProfileView不再依赖privacyLevel变量,因为它通过其初始化程序构造函数注入获得了必要的依赖关系。 从ProfileView中删除privacyLevel常数。

    注意:您会看到Xcode警告它在ProfileView_Previews中缺少参数。 不用担心 您很快就会解决。

    在这里,您可以开始看到这种方法的美。 现在,该视图完全不了解profile content背后的业务逻辑。 您可以提供ProfileContentProviderProtocol的任何实现,包括新的隐私级别,甚至可以模拟提供程序而无需更改任何代码!

    稍后您将对此进行验证。 首先,是时候设置Dependency Injection Container,以帮助将所有DI infrastructure收集到一个地方。

    1. Using a Dependency Injection Container

    现在,创建一个名为DIContainer.swift的新文件,并添加以下内容:

    protocol DIContainerProtocol {
      func register<Component>(type: Component.Type, component: Any)
      func resolve<Component>(type: Component.Type) -> Component?
    }
    
    final class DIContainer: DIContainerProtocol {
      // 1
      static let shared = DIContainer()
      
      // 2
      private init() {}
    
      // 3
      var components: [String: Any] = [:]
    
      func register<Component>(type: Component.Type, component: Any) {
        // 4
        components["\(type)"] = component
      }
    
      func resolve<Component>(type: Component.Type) -> Component? {
        // 5
        return components["\(type)"] as? Component
      }
    }
    

    以下是分步说明:

    • 1) 首先,创建一个类型为DIContainer的静态属性。
    • 2) 由于您将初始化程序标记为私有,因此实质上可以确保您的容器是单例的。 这样可以防止意外使用多个实例和意外行为,例如丢失某些依赖项。
    • 3) 然后,您创建一个词典以保留所有服务。
    • 4) 组件类型的字符串表示形式是字典中的键。
    • 5) 您可以再次使用该类型来解决必要的依赖关系。

    注意:本质上,DI Container与其他任何模式一样,是解决编程问题的一种方法。 您可以通过多种方式来实现它,包括第三方框架。

    接下来,要使您的容器处理依赖关系,请打开ProfileView.swift并更新ProfileView的初始化程序,如下所示:

    init(
      provider: ProfileContentProviderProtocol = 
        DIContainer.shared.resolve(type: ProfileContentProviderProtocol.self)!,
      user: User = DIContainer.shared.resolve(type: User.self)!
    ) {
      self.provider = provider
      self.user = user
    }
    

    现在,您的DIContainer默认提供了必要的参数。 但是,您始终可以自行传递依赖项以进行测试,也可以在容器中注册模拟的依赖项。

    接下来,在ProfileView下面找到ProfileView_Previews并进行更新:

    struct ProfileView_Previews: PreviewProvider {
      private static let user = Mock.user()
      static var previews: some View {
        ProfileView(
          provider: ProfileContentProvider(privacyLevel: .friend, user: user), 
          user: user)
      }
    }
    

    打开ProfileContentProvider.swift。 使用相同的方法更新ProfileContentProvider的初始化程序:

    init(
      privacyLevel: PrivacyLevel = 
        DIContainer.shared.resolve(type: PrivacyLevel.self)!,
      user: User = DIContainer.shared.resolve(type: User.self)!
    ) {
      self.privacyLevel = privacyLevel
      self.user = user
    }
    

    最后,您必须定义依赖项的初始状态以复制应用程序的行为,然后再开始对其进行操作。

    SceneDelegate.swift中,在profileView的初始化上方添加以下代码:

    let container = DIContainer.shared
    container.register(type: PrivacyLevel.self, component: PrivacyLevel.friend)
    container.register(type: User.self, component: Mock.user())
    container.register(
      type: ProfileContentProviderProtocol.self, 
      component: ProfileContentProvider())
    

    构建并运行。 虽然该应用看起来与以前完全一样,但您知道它的内部更漂亮。

    接下来,您将实现新功能。


    Extending the Functionality

    有时,用户希望向其好友列表中的人隐藏某些内容或功能。 也许他们张贴聚会中的图片,而他们只希望亲密的朋友看到。 或者,也许他们只想接收亲密朋友的视频通话。

    无论出于何种原因,赋予密友额外访问权限的能力都是一个很大的功能。

    要实现它,请转到PrivacyLevel.swift并添加另一种case

    enum PrivacyLevel: Comparable {
      case everyone, friend, closeFriend
    }
    

    接下来,更新将处理新隐私级别的provider。 转到ProfileContentProvider.swift并更新以下属性:

    var canStartVideoChat: Bool {
      privacyLevel > .friend
    }
    
    var photosView: AnyView {
      privacyLevel > .friend ? 
        AnyView(PhotosView(photos: user.photos)) : 
        AnyView(EmptyView())
    }
    

    使用此代码,您可以确保只有密友可以访问照片并发起视频通话。 您无需进行任何其他更改即可添加其他隐私级别。 您可以根据需要创建任意数量的隐私级别或组,为ProfileView提供provider,然后其他所有事务都将由您处理。

    现在,构建并运行:

    如您所见,.friend隐私级别的视频通话图标和“最近的照片”部分现在消失了。 您实现了目标!


    Adding User Preferences

    是否想尝试更复杂的用例? 如果您需要让provider根据用户的隐私首选项做出决定,该怎么办?

    为解决此问题,您将添加一个新屏幕,用户可以在其中确定谁可以访问其个人资料的每个部分,使用UserDefaults保存偏好设置,并在每次更新偏好设置时重新加载个人资料屏幕。 您将使用Combine框架来使其工作。

    注意:如果您想更熟悉Combine或更新知识,请看一下Combine: Getting Started tutorial教程。

    首先,打开PrivacyLevel.swift并将以下属性和方法添加到PrivacyLevel

    var title: String {
      switch self {
      case .everyone:
        return "Everyone"
      case .friend:
        return "Friends only"
      case .closeFriend:
        return "Close friends only"
      }
    }
    
    static func from(string: String) -> PrivacyLevel? {
      switch string {
      case everyone.title:
        return everyone
      case friend.title:
        return friend
      case closeFriend.title:
        return closeFriend
      default:
        return nil
      }
    }
    

    您将使用title在要创建的新偏好设置屏幕上显示隐私级别选项。 from(string :)帮助从保存的UserDefaults首选项重新创建PrivacyLevel

    现在,在项目导航器中右键单击Sociobox文件夹,然后选择Add Files to “Sociobox”…。选择PreferencesStore.swift,然后单击Add。打开文件并浏览代码。

    该类负责从UserDefaults中保存和读取用户偏好设置。

    您具有五个配置文件部分中的每一个的属性,以及用于重置首选项的方法。 PreferencesStoreProtocol符合ObservableObject协议,使您的store拥有一个publisher,只要使用@Published属性标记的任何属性发生更改,发布者都将发出该发布者。

    进行任何更改时,任何SwiftUI视图甚至常规类都可以订阅PreferencesStoreProtocol并重新加载其内容。

    接下来,您将添加Preferences Screen

    1. Adding the Preferences Screen

    现在,右键单击Views文件夹,然后再次选择Add Files to “Sociobox”…,以添加UserPreferencesView.swift。打开它,看看预览:

    这是新屏幕的外观。

    通过实现PreferencesStoreProtocol,使新屏幕保存用户首选项。 将UserPreferencesView的声明更新为以下内容:

    struct UserPreferencesView<Store>: View where Store: PreferencesStoreProtocol {
    

    像每种静态类型的编程语言一样,在编译时定义和检查类型。 这就是问题所在:您不知道Store在运行时将具有的确切类型,但不要惊慌! 您所知道的是,Store将符合PreferencesStoreProtocol。 因此,您告诉编译器Store将实现此协议。

    编译器需要知道要用于视图的特定类型。 稍后,当您创建UserPreferencesView实例时,需要在尖括号中使用特定类型而不是协议,如下所示:

    UserPreferencesView<PreferencesStore>()
    

    这样,可以在编译时检查类型。 现在,将以下属性和初始化程序添加到UserPreferencesView中:

    private var store: Store
    
    init(store: Store = DIContainer.shared.resolve(type: Store.self)!) {
      self.store = store
    }
    

    使用上面的代码,您可以让UserPreferencesView接收所需的依赖关系,而不是自己创建它。

    更新body属性以使用store访问用户首选项:

    var body: some View {
      NavigationView {
        VStack {
          PreferenceView(title: .photos, value: store.photosPreference) { value in
            store.photosPreference = value
          }
          PreferenceView(
            title: .friends, 
            value: store.friendsListPreference
          ) { value in
            store.friendsListPreference = value
          }
          PreferenceView(title: .feed, value: store.feedPreference) { value in
            store.feedPreference = value
          }
          PreferenceView(
            title: .videoCall, 
            value: store.videoCallsPreference
          ) { value in
            store.videoCallsPreference = value
          }
          PreferenceView(
            title: .message, 
            value: store.messagePreference
          ) { value in
            store.messagePreference = value
          }
          Spacer()
        }
      }.navigationBarTitle("Privacy preferences")
    }
    

    以下是代码细分:

    • 1) 垂直堆栈(vertical stack)中的每个PreferenceView都代表一个不同的配置文件部分,并带有一个下拉菜单来选择一个隐私级别。
    • 2) 从store中读取每个首选项的当前值。
    • 3) 当用户选择隐私选项时,将新值保存到store

    更新UserPreferencesView_PreviewsPreviews属性,以便您可以再次看到预览:

    static var previews: some View {
      UserPreferencesView(store: PreferencesStore())
    }
    

    SceneDelegate.swift中,将store dependency注册到您的容器中:

    container.register(type: PreferencesStore.self, component: PreferencesStore())
    

    2. Adding Combine

    接下来,转到ProfileContentProvider.swift并在文件顶部导入Combine

    import Combine
    

    然后,像使用UserPreferencesView一样更新其声明:

    final class ProfileContentProvider<Store>: ProfileContentProviderProtocol 
      where Store: PreferencesStoreProtocol {
    

    现在,更新ProfileContentProviderProtocol的声明:

    protocol ProfileContentProviderProtocol: ObservableObject {
    

    此代码使ProfileView可以订阅ProfileContentProvider中的更改,并在用户选择新的首选项时立即更新状态。

    ProfileContentProvider中,为store添加一个属性并替换初始化程序:

    private var store: Store
    private var cancellables: Set<AnyCancellable> = []
    
    init(
      privacyLevel: PrivacyLevel = 
        DIContainer.shared.resolve(type: PrivacyLevel.self)!,
      user: User = DIContainer.shared.resolve(type: User.self)!,
      // 1
      store: Store = DIContainer.shared.resolve(type: Store.self)!
    ) {
      self.privacyLevel = privacyLevel
      self.user = user
      self.store = store
    
      // 2
      store.objectWillChange.sink { _ in
        self.objectWillChange.send()
      }
      .store(in: &cancellables)
    }
    

    这是您所做的:

    • 1) DI Container提供PreferencesStore的实例。
    • 2) 您使用objectWillChange属性订阅PreferencesStoreProtocol的发布者。
    • 3) 当store中的属性发生更改时,也会使ProfileContentProviderProtocol的发布者发出。

    现在,更新ProfileContentProvider的属性以使用store的属性,而不使用PrivacyLevel枚举的实例:

    var canSendMessage: Bool {
      privacyLevel >= store.messagePreference
    }
    
    var canStartVideoChat: Bool {
      privacyLevel >= store.videoCallsPreference
    }
    
    var photosView: AnyView {
      privacyLevel >= store.photosPreference ? 
        AnyView(PhotosView(photos: user.photos)) : 
        AnyView(EmptyView())
    }
    
    var feedView: AnyView {
      privacyLevel >= store.feedPreference ? 
        AnyView(HistoryFeedView(posts: user.historyFeed)) : 
        AnyView(EmptyView())
    }
    
    var friendsView: AnyView {
      privacyLevel >= store.friendsListPreference ? 
        AnyView(UsersView(title: "Friends", users: user.friends)) : 
        AnyView(EmptyView())
    }
    

    除不再直接使用enum外,其他所有内容保持不变。

    3. Bringing It All Together

    要订阅provider中的更改,请打开ProfileView.swift并同时更改ProfileView的声明:

    struct ProfileView<ContentProvider>: View 
      where ContentProvider: ProfileContentProviderProtocol {
    

    更新provider属性以使用通用属性:

    @ObservedObject private var provider: ContentProvider
    

    SwiftUI视图中使用@ObservedObject时,您将订阅其发布者。 该视图在发出时会重新加载。

    也更新初始化器:

    init(
      provider: ContentProvider = 
        DIContainer.shared.resolve(type: ContentProvider.self)!,
      user: User = DIContainer.shared.resolve(type: User.self)!
    ) {
      self.provider = provider
      self.user = user
    }
    

    然后在body属性内的navigationTitle(“ Profile”)下方添加以下代码:

    .navigationBarItems(trailing: Button(action: {}) {
      NavigationLink(destination: UserPreferencesView<PreferencesStore>()) {
        Image(systemName: "gear")
      }
    })
    

    您添加了一个导航栏按钮,该按钮会将用户带到首选项屏幕。

    现在返回SceneDelegate.swift以更新依赖项注册。 由于您的很多协议和类都是通用的,因此将它们全部一起使用变得有点难以阅读。

    为了简化操作,请在scene(_:willConnectTo:options:)上方为提供者(provider)创建一个新的typealias

    typealias Provider = ProfileContentProvider<PreferencesStore>
    

    通过删除以下内容使用新的typealias

    container.register(
      type: ProfileContentProviderProtocol.self, 
      component: ProfileContentProvider())
    

    现在,添加以下_after_调用以注册PreferencesStore

    container.register(type: Provider.self, component: Provider())
    

    注意:您必须最后注册Provider,因为其初始化程序希望隐私级别,用户和存储区已经存在于DI Container中。

    <Provider>添加到profileView的初始化中:

    let profileView = ProfileView<Provider>()
    

    要获得可用的预览,请打开ProfileView.swift并在ProfileView_Previews中添加相同的设置:

    struct ProfileView_Previews: PreviewProvider {
      static var previews: some View {
        typealias Provider = ProfileContentProvider<PreferencesStore>
        let container = DIContainer.shared
        container.register(type: PrivacyLevel.self, component: PrivacyLevel.friend)
        container.register(type: User.self, component: Mock.user())
        container.register(
          type: PreferencesStore.self, 
          component: PreferencesStore())
        container.register(type: Provider.self, component: Provider())
        return ProfileView<Provider>()
      }
    }
    

    经过艰苦的工作,是时候看看它们如何一起工作了。 运行应用程序以查看结果:

    在本教程中,您学习了Dependency Injection模式以及如何在项目中构建和应用它。 根据您正在从事的项目,您可以考虑使用第三方解决方案。

    要了解更多信息,请阅读我们的Swinject教程。 即使您没有使用第三方框架进行依赖项注入(dependency injection),您也会发现一些方便的测试示例。

    后记

    本篇主要讲述了基于SwiftUI简单App的Dependency Injection应用,感兴趣的给个赞或者关注~~~

    相关文章

      网友评论

          本文标题:SwiftUI框架详细解析 (十六) —— 基于SwiftUI简

          本文链接:https://www.haomeiwen.com/subject/clranktx.html