VIPERアーキテクチャパターンの概要

VIPERアーキテクチャパターンは、MVCまたはMVVMの代替です。 SwiftUIとCombineフレームワークは、複雑なUiを構築し、アプリの周りにデータを移動する作業を迅速に行う強力な組み合わせを作成しますが、彼らはまた、アーキテクチャに

すべてのアプリロジックがSwiftUIビューに入るべきだというのは一般的な信念ですが、そうではありません。

VIPERはこのシナリオに代わるものを提供し、SwiftUIと組み合わせて使用して、ユーザーインターフェイス、ビジネスロジック、データストレージ、ネットワークなど、必要な これらは、テスト、維持、拡張が容易になります。

このチュートリアルでは、VIPERアーキテクチャパターンを使用してアプリを構築します。 視覚的に興味深い計画された簡単なRoadtrips:アプリはまた、便利にバイパーと呼ばれています。 賢い、右? :]

これは、ユーザーがルートにウェイポイントを追加することにより、道路の旅行を構築することができます。 途中で、SwiftUIについて学び、iOSプロジェクトのために組み合わせることもできます。

VIPERアプリのメイン画面

はじめに

チュートリアルの上部または下部にある材料のダウンロードボタンからプロジェクト スタータープロジェクトを開きます。 これには、開始するためのコードが含まれています。

  • ContentViewアプリの他のビューをビルドするときに起動します。
  • Functional Viewsグループには、TripListCellで使用される特別な”分割画像”ビューであるMapKitマップビューをラップするためのヘルパービューがいくつかあります。 あなたは少しで画面にこれらを追加することがあります。
  • Entitiesグループには、データモデルに関連するクラスが表示されます。 TripとWaypointは、後でVIPERアーキテクチャのエンティティとして機能します。 そのため、それらはデータを保持するだけで、機能的なロジックは含まれません。
  • データソースグループには、データを保存またはロードするためのヘルパー関数があります。
  • WaypointModuleグループで好きな場合は、先を覗いてください。 これには、ウェイポイント編集画面のVIPER実装があります。 あなたはこのチュートリアルの最後までにアプリを完了することができますので、それはスターターに含まれています。

このサンプルでは、許可された写真共有サイトを使用しています。 アプリに画像を取り込むには、無料のアカウントを作成し、APIキーを取得する必要があります。 アカウントを作成するには、https://.com/accounts/register/の指示に従ってください。 次に、ApiキーをImageDataProviderにあるapiKey変数にコピーします。スウィフト あなたは検索画像の下のAPIドキュメントでそれを見つけることができます。今すぐビルドして実行すると、あまりにも興味深いものは表示されません。

スタータープロジェクトでバイパーアプリ

しかし、チュートリアルの終わりまでに、あなたは完全に機能する道路旅行計画アVIPERとは何ですか?

VIPERはMVCやMVVMのようなアーキテクチャパターンですが、コードを単一の責任でさらに分離します。 AppleスタイルのMVCは、すべてのロジックをUIViewControllerサブクラスに入れるように開発者に動機を与えます。 VIPERは、それ以前のMVVMと同様に、この問題を解決しようとしています。

VIPERの各文字は、View、Interactor、Presenter、Entity、Routerというアーキテクチャのコンポーネントを表します。

  • ビューはユーザーインターフェイスです。 これはSwiftUIViewに対応します。
  • Interactorは、プレゼンターとデータの間を仲介するクラスです。 それは発表者からの方向を取ります。
  • プレゼンターはアーキテクチャの”traffic cop”であり、ビューとインタラクタの間でデータを指示し、ユーザーアクションを実行し、ビュー間でユーザーを移動するためにrouterを呼
  • エンティティはアプリケーションデータを表します。
  • ルーターは画面間のナビゲーションを処理します。 これは、ビューに新しいビューが表示されるSwiftUIとは異なります。

この分離は、”Uncle”Bob Martinのクリーンなアーキテクチャのパラダイムから生まれています。

VIPER Diagram

ダイアグラムを見ると、ビューとエンティティの間を流れるデータの完全なパスがあることがわかります。

SwiftUIには独自の独断的なやり方があります。 Viperの責任のドメインオブジェクトへのマッピングは、これをUIKitアプリのチュートリアルと比較すると異なります。

アーキテクチャの比較

人々はしばしばMVCとMVVMでVIPERについて議論しますが、それらのパターンとは異なります。

MVC、またはModel-View-Controllerは、ほとんどの人が2010のiOSアプリアーキテクチャに関連付けるパターンです。 このアプローチでは、ストーリーボードでビューを定義し、コントローラは関連付けられたUIViewControllerサブクラスです。 コントローラーはビューを変更し、ユーザー入力を受け入れ、モデルと直接対話します。 コントローラは、ビューロジックとビジネスロジックで肥大化します。

MVVMは、ビューモデル内のビューロジックとビジネスロジックを分離する一般的なアーキテクチャです。 ビューモデルはモデルと対話します。

大きな違いは、ビューモデルは、ビューコントローラとは異なり、ビューとモデルへの一方向参照のみを持っていることです。 MVVMはSwiftUIに適しており、このトピックに関するチュートリアル全体があります。

VIPERは、ビューロジックをデータモデルロジックから分離することによってさらに一歩進んでいます。 プレゼンターのみがビューと対話し、インタラクタのみがモデル(エンティティ)と対話します。 発表者およびinteractorは互いに調整する。 プレゼンターは表示とユーザーアクションに関係し、インタラクタはデータの操作に関係しています。

viper snake,for fun

エンティティを定義する

VIPERはこのアーキテクチャの楽しい頭字語ですが、その順序は説明的ではありません。

画面上に何かを取得する最速の方法は、エンティティから開始することです。 エンティティは、プロジェクトのデータオブジェクトです。 この場合、主なエンティティはTripであり、Trip内のストップであるウェイポイントのリストが含まれています。

アプリには、旅行のリストを保持するDataModelクラスが含まれています。 このモデルでは、ローカルの永続性にJSONファイルを使用しますが、UIレベルのコードを変更することなく、リモートバックエンドで置き換えることができま 永続化レイヤーのように、ある部分を変更すると、コードの他の領域から分離されます。

Interactorの追加

TripListInteractorという名前の新しいSwiftファイルを作成します。スウィフト

ファイルに次のコードを追加します:これにより、interactorクラスが作成され、後で使用するDataModelが割り当てられます。Presenterの設定

ここで、TripListPresenterという名前の新しいSwiftファイルを作成します。スウィフト これはpresenterクラスのためのものです。 プレゼンターは、UIにデータを提供し、ユーザーアクションを仲介することを気にします。

次のコードをファイルに追加します。

import SwiftUIimport Combineclass TripListPresenter: ObservableObject { private let interactor: TripListInteractor init(interactor: TripListInteractor) { self.interactor = interactor }}

これにより、interactorへの参照を持つpresenterクラスが作成されます。ビューにデータを入力するのはプレゼンターの仕事なので、データモデルからの旅行のリストを公開する必要があります。

クラスに新しい変数を追加します。

@Published var trips: = 

これは、ユーザーがビューに表示されるトリップのリストです。 @Publishedプロパティラッパーで宣言することで、ビューはプロパティの変更をリッスンし、自動的に更新することができます。

次のステップは、このリストをinteractorからのデータモデルと同期させることです。 まず、次のヘルパプロパティを追加します:

private var cancellables = Set<AnyCancellable>()

このセットは、コンバインサブスクリプションを格納する場所であり、その有効期間はクラスのものに関連付けられています。

次のコードをinit(interactor:)の最後に追加します。

interactor.model.$trips .assign(to: \.trips, on: self) .store(in: &cancellables)

interactor.model.$tripstripstripscancellablesに格納されているため、後でクリーンアップできます。

ビューの構築

最初のビュー、トリップリストビューを構築する必要があります。

プレゼンターを使用したビューの作成

SwiftUIビューテンプレートから新しいファイルを作成し、TripListViewという名前を付けます。スウィフトp>

次のプロパティをTripListView:p>

@ObservedObject var presenter: TripListPresenter

プレゼンターをビューにリンクします。 次に、TripListView_Previews.previewsの本文を次のように変更してプレビューを修正します。

let model = DataModel.samplelet interactor = TripListInteractor(model: model)let presenter = TripListPresenter(interactor: interactor)return TripListView(presenter: presenter)

ここで、TripListView.bodyの内容を次のように置き換えます。

List { ForEach (presenter.trips, id: \.id) { item in TripListCell(trip: item) .frame(height: 240) }}

これは

List { ForEach (presenter.trips, id: \.id) { item in TripListCell(trip: item) .frame(height: 240) }}

これは

let model = DataModel.samplelet interactor = TripListInteractor(model: model)let presenter = TripListPresenter(interactor: interactor)return TripListView(presenter: presenter)

これは

let model = DataModel.samplelet interactor = TripListInteractor(model: model)let presenter = TripListPresenter(interactor: interactor)return TripListView(presenter: presenter)

これは

let model = DataModel.samplelet interactor = TripListInteractor(model: model)let presenter = TripListPresenter(interactor: interactor)return TripListView(presenter: presenter)

これは

let model = DataModel.samplelet interactor = TripListInteractor(model: model)let presenter = TripListPresenter(interactor: interactor)return TripListView(presenter: presenter)

Listプレゼンターのトリップが列挙され、それぞれに事前に指定されたTripListCellが生成されます。

トリップリストビューのプレビューウィンドウ

ビューからモデルを変更する

これまで、エンティティからプレゼンタ VIPERパターンは、データモデルを操作するためにユーザーアクションを元に戻すときにさらに便利です。それを確認するには、新しい旅行を作成するためのボタンを追加します。

それを確認するには、新しい旅行を作成するためのボタンを追加します。最初に、TripListInteractorのクラスに次を追加します。スウィフト:これは、モデルのpushNewTrip()をラップし、tripsリストの上部に新しいTripを作成します。次に、TripListPresenterで。swift、これをクラスに追加します:

func makeAddNewButton() -> some View { Button(action: addNewTrip) { Image(systemName: "plus") }}func addNewTrip() { interactor.addNewTrip()}

これは、システム+addNewTrip()。 これにより、アクションがinteractorに転送され、interactorはデータモデルを操作します。TripListViewに戻ります。

swiftとList閉じ括弧の後に次を追加します:

.navigationBarTitle("Roadtrips", displayMode: .inline).navigationBarItems(trailing: presenter.makeAddNewButton())

ボタンとタイトルをナビゲーションバーに追加します。 ここで、returnTripListView_Previewsを次のように変更します。

return NavigationView { TripListView(presenter: presenter)}

これにより、ナビゲーションバーをプレビューモードで表示できます。

ライブプレビューを再開してボタンを表示します。

ライブプレビューのボタンを持つトリップリスト

アクションでそれを見て

今戻って、アプリケーションの残りの部分にTripListView

Contentviewを開きます。スウィフト viewVStackを次のように置き換えます。

TripListView(presenter: TripListPresenter(interactor: TripListInteractor(model: model)))

これにより、プレゼンターとインタラクタと一緒にビューが作成されます。 今すぐビルドして実行します。

+ボタンをタップすると、リストに新しい旅行が追加されます。

新しい旅行が追加された旅行リスト

旅行を削除する

旅行を作成したユーザーは、間違いを犯した場合や旅行が終わった データパスを作成したので、画面にアクションを追加するのは簡単です。これにより、データモデルのtrips@Publishedプロパティであるため、UIは変更へのサブスクリプションのために自動的に更新されます。

TripListPresenterで、次のように追加します。

func deleteTrip(_ index: IndexSet) { interactor.deleteTrip(index)}

これにより、deleteコマンドがインターアクタに転送されます。最後に、TripListViewForEachの終了中括弧の後に次を追加します:SwiftUIの項目に.onDeleteを追加すると、自動的にスワイプで動作を削除できます。 その後、アクションはプレゼンターに送信され、チェーン全体がキックオフされます。

ビルドして実行すると、tripsを削除できるようになりました!

onDeleteでは、削除アクションが有効になります。

詳細ビューへのルーティング

今度はVIPERのルータ部分に追加します。

ルーターを使用すると、ユーザーはトリップリストビューからトリップ詳細ビューに移動できます。 トリップの詳細ビューには、ルートのマップと一緒にウェイポイントのリストが表示されます。

ユーザーは、この画面からウェイポイントのリストとトリップ名を編集することができます。p>

イェーイルーター!

トリップ詳細画面の設定

詳細画面を表示する前に、それを作成する必要があります。

前の例に続いて、新しいSwiftファイルTripDetailPresenterを作成します。スウィフトとTripDetailInteractor。swiftとTripdetailviewという名前のSwiftUIビュー。スウィフトp>

TripDetailInteractorの内容を次のように設定します:

import Combineimport MapKitclass TripDetailInteractor { private let trip: Trip private let model: DataModel let mapInfoProvider: MapDataProvider private var cancellables = Set<AnyCancellable>() init (trip: Trip, model: DataModel, mapInfoProvider: MapDataProvider) { self.trip = trip self.mapInfoProvider = mapInfoProvider self.model = model }}

これにより、トリップ詳細画面のインタラクタ用の新しいクラスが作成されます。 これは、個々のTripとMapKitからのマップ情報の2つのデータソースと対話します。 また、後で追加するキャンセル可能なサブスクリプションのセットもあります。次に、TripDetailPresenterで、その内容を次のように設定します。

import SwiftUIimport Combineclass TripDetailPresenter: ObservableObject { private let interactor: TripDetailInteractor private var cancellables = Set<AnyCancellable>() init(interactor: TripDetailInteractor) { self.interactor = interactor }}

これにより、interactorとcancellable setの参照を持つスタブプレゼンターが作成されます。 あなたは少しでこれを構築します。p>

TripDetailViewに、次のプロパティを追加します:p>

@ObservedObject var presenter: TripDetailPresenter

これは、ビュー内のプレゼンターへの参照を追加します。

をプレビュビルに変更するスタブを

static var previews: some View { let model = DataModel.sample let trip = model.trips let mapProvider = RealMapDataProvider() let presenter = TripDetailPresenter(interactor: TripDetailInteractor( trip: trip, model: model, mapInfoProvider: mapProvider)) return NavigationView { TripDetailView(presenter: presenter) } }

現在のビューを構築、プレビューでは”Hello,World!”

デフォルトのビュープレビューだけ

ルーティング

詳細ビューを構築する前に、トリップリストからルータを介してアプリの残りのTripListRouterという名前の新しいSwiftファイルを作成します。スウィフト

その内容を次のように設定します。

:このクラスは、インタラクタとプレゼンターが入力された新しいTripDetailViewを出力します。 ルーターは、ある画面から別の画面への移行を処理し、次のビューに必要なクラスを設定します。

命令的なUIパラダイムでは、言い換えれば、UIKitでは、ルーターがview controllerを提示したり、seguesをアクティブにしたりします。 SwiftUIは、すべてのターゲットビューを現在のビューの一部として宣言し、ビューステートに基づいて表示します。 VIPERをSwiftUIにマップするために、ビューはビューの表示/非表示を担当し、ルーターはdestination view builderであり、presenterはそれらの間の座標を調整します。

でTripListPresenter。swift、ルータをプロパティとして追加します。

private let router = TripListRouter()

これで、プレゼンターの一部としてルータを作成しました。次に、次のメソッドを追加します。

func linkBuilder<Content: View>( for trip: Trip, @ViewBuilder content: () -> Content ) -> some View { NavigationLink( destination: router.makeDetailView( for: trip, model: interactor.model)) { content() }}

これにより、ルータが提供する詳細ビューにNavigationLinkNavigationViewdestinationをナビゲーションスタックにプッシュするボタンになります。

contentブロックは任意のSwiftUIビューにすることができます。 しかし、この場合、TripListViewTripListCellを提供します。TripListViewに移動します。

swiftとの内容を変更ForEachに:

self.presenter.linkBuilder(for: item) { TripListCell(trip: item) .frame(height: 240)}

これは、使用していますNavigationLinkプレゼンターから、その内容としてセルを設定し、リ

を構築-運用し、この場合、ユーザがタップの細胞でルートへ”こんにちは世界”TripDetailView.

詳細画面Hello World

詳細ビューの仕上げ

ユーザーがルートを見てウェイポイントを編集できるように、詳細ビューに記入する必要こんにちこんがいくつかあります。こんにちこんにちこんにちこんにちこんにちこんにちこんにちこんにちこんにちこんにちこんにちこんにちは、まだあります。

旅行のタイトルを追加することから始めます:

TripDetailInteractorPublisherバージョンだけが公開されます。

また、次を追加します。

func setTripName(_ name: String) { trip.name = name}func save() { model.save()}

最初の方法では、プレゼンターがトリップ名を変更でき、2番目の方法ではモデルが永続レイヤーに保存されます。 ここで、TripDetailPresenterに移動します。 次のプロパティを追加します。

@Published var tripName: String = "No name"let setTripName: Binding<String>

これらは、ビューがトリップ名を読み取って設定するためのフックを提供します。次に、initメソッドに次を追加します。

// 1setTripName = Binding<String>( get: { interactor.tripName }, set: { interactor.setTripName($0) })// 2interactor.tripNamePublisher .assign(to: \.tripName, on: self) .store(in: &cancellables)

このコード:

  1. トリップ名を設定するためのバインディングを作成します。 のTextFieldこれをビューで使用して、値から読み書きできます。
  2. は、interactorのパブリッシャからプレゼンターのtripNameプロパティにトリップ名を割り当てます。 これにより、値の同期が維持されます。

トリップ名をこのようなプロパティに分離すると、無限ループの更新を作成せずに値を同期することができます。

次に、これを追加します。

func save() { interactor.save()}

これは、ユーザーが編集した詳細を保存できるように保存機能を追加します。最後に、TripDetailViewbodyを次のように置き換えます。

var body: some View { VStack { TextField("Trip Name", text: presenter.setTripName) .textFieldStyle(RoundedBorderTextFieldStyle()) .padding() } .navigationBarTitle(Text(presenter.tripName), displayMode: .inline) .navigationBarItems(trailing: Button("Save", action: presenter.save))}

VStackTripDetailViewTextFieldトリップ名を編集するためのものです。 ナビゲーションバー修飾子は、プレゼンターの公開されたtripNameを使用してタイトルを定義するため、ユーザーの入力に応じて更新され、変更を永続化する保

ビルドと実行、そして今、あなたは旅行のタイトルを編集することができます。

詳細ビューで名前を編集

旅行名を編集した後に保存すると、アプリを再起動した後に変更が表示されます。

変更は保存後も持続します

マップの第二のプレゼンターを使用して

画面にウィジェットを追加すると、次の同じパターンに従います。

  • interactorへの機能の追加。
  • プレゼンターを介して機能をブリッジします。
  • ビューにウィジェットを追加します。

TripDetailInteractorに移動し、次のプロパティを追加します。

@Published var totalDistance: Measurement<UnitLength> = Measurement(value: 0, unit: .meters)@Published var waypoints: = @Published var directions: = 

これらは、トリップ内のウェイポイントに関する次の情報を提供します。Measurement、ウェイポイントのリスト、およびそれらのウェイポイントを接続する方向のリスト。

次に、init(trip:model:mapInfoProvider:)の最後にfollowサブスクリプションを追加します。

trip.$waypoints .assign(to: \.waypoints, on: self) .store(in: &cancellables)trip.$waypoints .flatMap { mapInfoProvider.totalDistance(for: $0) } .map { Measurement(value: $0, unit: UnitLength.meters) } .assign(to: \.totalDistance, on: self) .store(in: &cancellables)trip.$waypoints .setFailureType(to: Error.self) .flatMap { mapInfoProvider.directions(for: $0) } .catch { _ in Empty<, Never>() } .assign(to: \.directions, on: self) .store(in: &cancellables)

これは、トリップのウェイポイントの変更に基づいて三つの別々のアクショ 最初のものはinteractorのwaypointリストへのコピーにすぎません。 第二は、すべてのウェイポイントの合計距離を計算するためにmapInfoProviderを使用します。 そして、第三は、ウェイポイント間の方向を取得するために、同じデータプロバイダを使用しています。

プレゼンターは、これらの値を使用してユーザーに情報を提供します。

TripDetailPresenterに移動し、これらのプロパティを追加します。

@Published var distanceLabel: String = "Calculating..."@Published var waypoints: = 

ビューはこれらのプロパティを使用します。 データの変更を追跡するために配線するには、init(interactor:)の最後に次のものを追加します:

interactor.$totalDistance .map { "Total Distance: " + MeasurementFormatter().string(from: $0) } .replaceNil(with: "Calculating...") .assign(to: \.distanceLabel, on: self) .store(in: &cancellables)interactor.$waypoints .assign(to: \.waypoints, on: self) .store(in: &cancellables)

最初のサブスクリプションは、インタラクタからの生の距離を取り、ビューに表示するためにフォーマットし、第二はウェイポイ

マップビューを考慮する

詳細ビューに移動する前に、マップビューを考慮します。 このウィジェットは、他のものよりも複雑です。

地理的特徴を描画することに加えて、アプリはまた、各ポイントとそれらの間のルートのためのピンをオーバーレイします。

これは、プレゼンテーションロジックの独自のセットを呼び出します。 あなたはTripDetailPresenterTripMapViewPresenterTripDetailInteractorを再利用します。Tripmapviewpresenterという名前の新しいSwiftファイルを作成します。スウィフト その内容を次のように設定します。

import MapKitimport Combineclass TripMapViewPresenter: ObservableObject { @Published var pins: = @Published var routes: = let interactor: TripDetailInteractor private var cancellables = Set<AnyCancellable>() init(interactor: TripDetailInteractor) { self.interactor = interactor interactor.$waypoints .map { $0.map { let annotation = MKPointAnnotation() annotation.coordinate = $0.location return annotation } } .assign(to: \.pins, on: self) .store(in: &cancellables) interactor.$directions .assign(to: \.routes, on: self) .store(in: &cancellables) }}

ここで、マッププレゼンターは、注釈とルートを保持するために二つの配列を公開します。 init(interactor:)waypointsMKPointAnnotationdirectionsroutes配列にコピーします。presenterを使用するには、Tripmapviewという名前の新しいSwiftUIビューを作成します。スウィフト その内容を次のように設定します。

import SwiftUIstruct TripMapView: View { @ObservedObject var presenter: TripMapViewPresenter var body: some View { MapView(pins: presenter.pins, routes: presenter.routes) }}#if DEBUGstruct TripMapView_Previews: PreviewProvider { static var previews: some View { let model = DataModel.sample let trip = model.trips let interactor = TripDetailInteractor( trip: trip, model: model, mapInfoProvider: RealMapDataProvider()) let presenter = TripMapViewPresenter(interactor: interactor) return VStack { TripMapView(presenter: presenter) } }}#endif

これはヘルパーMapViewpreviews構造体は、アプリがマップだけをプレビューするために必要なバイパーチェーンを構築します。 ライブプレビューを使用してマップを適切に表示する:

Tripmapviewとプレビューウィンドウ

アプリにマップを追加するには、最初のTripDetailPresenterに次のメソッドを追加します。

func makeMapView() -> some View { TripMapView(presenter: TripMapViewPresenter(interactor: interactor))}

これは、そのプレゼンターとそれを提供し、マップビューを行います。次に、tripdetailviewを開きます。スウィフト

次をVStackTextField

presenter.makeMapView()Text(presenter.distanceLabel)

ビルドして実行して、画面上のマッ:

アプリでの作業マップビュー

ウェイポイントの編集

最後の機能は、あなた自身の旅行を作ることができるようにウェイポ 旅行の詳細ビューでリストを並べ替えることができます。 ただし、新しいウェイポイントを作成するには、ユーザーが名前を入力するための新しいビューが必要です。新しいビューに到達するには、ルータが必要です。 Tripdetailrouterという名前の新しいSwiftファイルを作成します。スウィフトこのコードを新しいファイルに追加します。

import SwiftUIclass TripDetailRouter { private let mapProvider: MapDataProvider init(mapProvider: MapDataProvider) { self.mapProvider = mapProvider } func makeWaypointView(for waypoint: Waypoint) -> some View { let presenter = WaypointViewPresenter( waypoint: waypoint, interactor: WaypointViewInteractor( waypoint: waypoint, mapInfoProvider: mapProvider)) return WaypointView(presenter: presenter) }}

これにより、すでに設定され、準備ができているWaypointViewが作成されます。

ルーターを手元に置いて、TripDetailInteractorに移動します。swift、および次のメソッドを追加します。

func addWaypoint() { trip.addWaypoint()}func moveWaypoint(fromOffsets: IndexSet, toOffset: Int) { trip.waypoints.move(fromOffsets: fromOffsets, toOffset: toOffset)}func deleteWaypoint(atOffsets: IndexSet) { trip.waypoints.remove(atOffsets: atOffsets)}func updateWaypoints() { trip.waypoints = trip.waypoints}

これらのメソッドは自己記述的です。 ウェイポイントを追加、移動、削除、および更新します。 次に、これらをTripDetailPresenterTripDetailPresenterに、このプロパティを追加します。

private let router: TripDetailRouter

これはルータを保持します。 これをinit(interactor:)の先頭に追加して作成します。

self.router = TripDetailRouter(mapProvider: interactor.mapInfoProvider)

これにより、ウェイポイントエディタで使用するルータが作成されます。 次に、次のメソッドを追加します:p>

func addWaypoint() { interactor.addWaypoint()}func didMoveWaypoint(fromOffsets: IndexSet, toOffset: Int) { interactor.moveWaypoint(fromOffsets: fromOffsets, toOffset: toOffset)}func didDeleteWaypoint(_ atOffsets: IndexSet) { interactor.deleteWaypoint(atOffsets: atOffsets)}func cell(for waypoint: Waypoint) -> some View { let destination = router.makeWaypointView(for: waypoint) .onDisappear(perform: interactor.updateWaypoints) return NavigationLink(destination: destination) { Text(waypoint.name) }}

最初の三つは、ウェイポイントの操作の一部です。 最後のメソッドは、ルーターを呼び出してウェイポイントのウェイポイントビューを取得し、それをNavigationLinkに配置します。

最後に、TripDetailViewVStackTextに以下を追加して、これをユーザーに表示します。:

HStack { Spacer() EditButton() Button(action: presenter.addWaypoint) { Text("Add") }}.padding()List { ForEach(presenter.waypoints, content: presenter.cell) .onMove(perform: presenter.didMoveWaypoint(fromOffsets:toOffset:)) .onDelete(perform: presenter.didDeleteWaypoint(_:))}

これにより、ビューに次のコントp>

  • anEditButtonユーザーがウェイポイントを移動または削除できるように、リストを編集モードにします。
  • 追加Buttonプレゼンターを使用してリストに新しいウェイポイントを追加します。
  • aListForEachonMoveonDeleteアクションを定義します。

ビルドして実行すると、旅行をカスタマイズできるようになりました! 変更内容は必ず保存してください。

ウェイポイントは、詳細画面に追加
ウェイポイントエディタ

モジュールを作る

VIPERを使用すると、モジュールにプレゼンタ、インタラクタ、ビュー、ルータおよび関連コードをグループ化することができます。

従来、モジュールはpresenter、interactor、およびrouterのインターフェイスを単一のコントラクトで公開していました。 これはSwiftUIではビューフォワードなのであまり意味がありません。 各モジュールを独自のフレームワークとしてパッケージ化したい場合を除き、代わりにモジュールをグループとして概念化できます。

TripListViewを取ります。スイフト、トリプリストプレゼンター。スウィフトトリプリストインタラクタスウィフトとトリプルストルーター。swiftとTripListModuleという名前のグループにそれらを一緒にグループ化します。

詳細クラスについても同じことを行います:TripDetailView。スイフト、TripDetailPresenter。スイフト、トリップデタイルインタラクタ。スイフト、TripMapViewPresenter。スイフト、トリップマップビュー。スイフト、およびTripDetailRouter。スウィフト

TripDetailModuleという新しいグループに追加します。

モジュールは、コードをきれいに分離しておくのに良い方法です。 経験則として、モジュールは概念的な画面/機能でなければならず、ルータはモジュール間でユーザーを引き渡す必要があります。

ここからどこに行くのですか?

チュートリアルの上部または下部にある材料のダウンロードボタンをクリックして、完成したプロジェクトファイルをダウンロー

分離バイパー支持の利点の一つは、テスト容易性にあります。 Interactorをテストして、データモデルを読み取って操作できるようにすることができます。 また、プレゼンターを独立してテストしてビューを変更し、ユーザーのアクションに応答することができます。 あなた自身で試してみるのが楽しい運動と考えてください!

あなた自身で試してみるのが楽しい運動と考えてください!

Combineの無効電力とSwiftUIでのネイティブサポートのため、interactorレイヤーとpresenterレイヤーが比較的薄いことに気づいたかもしれません。 彼らは懸念を分離しますが、ほとんどの場合、抽象化層を介してデータを渡すだけです。

SwiftUIでは、presenterとinteractorの機能を単一のObservableObjectに折りたたむ方が少し自然です。別のアプローチについては、MVVM with Combine Tutorial for iOSをお読みください。私たちは、あなたがこのチュートリアルを楽しんで願っています!

あなたが質問やコメントを考える場合は、以下の議論にそれらをドロップします。 私たちはあなたの好きな建築とSwiftUIの時代に何が変わったのかを聞いてみたいと思います。h4>

raywenderlich.com 週次

このページは

raywenderlich.com ニュースレターは、あなたがモバイル開発者として知っておく必要があるすべてのものに最新の滞在する最も簡単な方法です。

私たちのチュートリアルやコースの毎週のダイジェストを取得し、ボーナスとして無料の詳細な電子メールコースを受け取ります!

平均評価

4.3/5

このコンテンツの評価を追加

サインインして評価を追加

33評価

コメントを残す

メールアドレスが公開されることはありません。