Swiftuiのコーディネーターパターンのシンプルで強力でエレガントな実装。 Stinsenは100%Swiftuiを使用して書かれており、iOS、TVOS、WatchOS、MacOSデバイスでシームレスに機能します。
UIKITでのルーティングは、より大きなサイズのアプリケーションを操作する場合、またはMVVMなどのアーキテクチャパターンを適用しようとするときに、エレガントに行うのが難しい場合があることを知っています。残念ながら、SwiftuiがNavigationLinkと同じ問題の多くに苦しんでいます。VavigationLinkLive The View-Layerなどの概念は、流れやルートの明確な概念はまだありません。スティンセンはこれらの痛みを軽減するために作成され、コーディネーターパターンの実装です。 Swiftuiで書かれているため、完全にクロスプラットフォームであり、 @EnvironmentObjectなどのネイティブツールを使用しています。目標は、スティンセンをスウィフトゥイの不足しているツールのように感じさせ、そのコーディングスタイルと一般原則に準拠することです。
通常、Swiftuiでは、ViewはNavigationLinkを使用してナビゲーションスタックに他のビューを追加することを処理する必要があります。ここで私たちが持っているのは、ビューが間でナビゲートできる他のすべてのビューを事前に知る必要があるため、ビュー間の緊密な結合です。また、ビューは単一応答の原則(SRP)に違反しています。 2015年のNSSPAIN会議でSoroush KhanlouによってiOSコミュニティに提示されたコーディネーターパターンを使用して、この責任をより高いクラスであるコーディネーターに委任することができます。
ナビゲーションスタックを使用した例:
final class UnauthenticatedCoordinator : NavigationCoordinatable {
let stack = NavigationStack ( initial : UnauthenticatedCoordinator . start )
@ Root var start = makeStart
@ Route ( . modal ) var forgotPassword = makeForgotPassword
@ Route ( . push ) var registration = makeRegistration
func makeRegistration ( ) -> RegistrationCoordinator {
return RegistrationCoordinator ( )
}
@ ViewBuilder func makeForgotPassword ( ) -> some View {
ForgotPasswordScreen ( )
}
@ ViewBuilder func makeStart ( ) -> some View {
LoginScreen ( )
}
} @Route Sは、現在のコーディネーターと実行される遷移から実行できるすべての可能なルートを定義します。右側の値は、ルーティング時に実行される工場関数です。この関数は、SwiftUIビューまたは別のコーディネーターのいずれかを返すことができます。 @Rootは、遷移がなく、コーディネーターのNavigationStackスタックの最初のビューを定義するために使用される別のタイプのルートを使用します。
Stinsen Aut of the Boxには、コーディネーターが実装できる2種類のCoordinatableプロトコルがあります。
NavigationCoordinatableナビゲーションフロー用。ナビゲーションスタックをプッシュしたい場合は、これらをNavigationViewCoordinatorに包むようにしてください。TabCoordinatableタブビュー用。さらに、 Stinsenには、 ViewWrapperCoordinatorとNavigationViewCoordinator 2つのコーディネーターもいます。 ViewWrapperCoordinatorは、サブクラスまたはすぐに使用してコーディネーターをビューでラップするコーディネーターであり、 NavigationViewCoordinator 、コーディネーターをNavigationViewでラップするViewWrapperCoordinatorサブクラスです。
コーディネーターのビューは.view()を使用して作成できます。したがって、コーディネーターをユーザーに表示するために、次のようなことをするだけです。
struct StinsenApp : App {
var body : some Scene {
WindowGroup {
MainCoordinator ( ) . view ( )
}
}
} Stinsenを使用して、アプリ全体、またはアプリの一部の一部に電力を供給できます。通常のSwiftui NavigationLinkを使用し、 Stinsenが管理するビュー内にモーダルシートを表示することができます。
コーディネーターとビューの両方を参照するルーターを使用して、ビューから移行を実行できます。ビュー内では、 @EnvironmentObjectを使用してルーターをフェッチできます。ルーターを使用すると、他のルートに移行できます。
struct TodosScreen : View {
@ EnvironmentObject var todosRouter : TodosCoordinator . Router
var body : some View {
List {
/* ... */
}
. navigationBarItems (
trailing : Button (
action : {
// Transition to the screen to create a todo:
todosRouter . route ( to : . createTodo )
} ,
label : {
Image ( systemName : " doc.badge.plus " )
}
)
)
}
}また、ツリーの前に表示されたコーディネーターを参照するルーターを取得することもできます。たとえば、 TabView内にあるビューからタブを切り替えることができます。
ルーティングはコーディネーター自体で直接実行できます。これは、コーディネーターにロジックを持っている場合、またはコーディネーターを渡す場合に役立ちます。
final class MainCoordinator : NavigationCoordinatable {
@ Root var unauthenticated = makeUnauthenticated
@ Root var authenticated = makeAuthenticated
/* ... */
init ( ) {
/* ... */
cancellable = AuthenticationService . shared . status . sink { [ weak self ] status in
switch status {
case . authenticated ( let user ) :
self ? . root ( . authentiated , user )
case . unauthenticated :
self ? . root ( . unauthentiated )
}
}
}
}ルーター/コーディネーターから実行できるアクションは、使用するコーディネーターの種類に依存します。たとえば、 NavigationCoordinatableを使用すると、実行できる関数の一部は次のとおりです。
popLastスタックから最後のアイテムを削除します。スティンセンは、ビューがモダンに提示されているかプッシュされたかにかかわらず、両方に同じ関数が使用されているかどうかに関心がないことに注意してください。pop - スタックからビューを削除します。この関数は、ルーターだけがあなたがポップしようとしているビューについて知っているため、ルーターによってのみ実行できます。popToRootスタックをクリアします。root - ルートを変更します(つまり、スタックの最初のビュー)。ルートがすでにアクティブルートである場合、何もしません。route - 別のルートに移動します。focusFirst最初のアイテムから始まるスタックに存在する場合、指定されたルートを見つけます。見つかった場合、その後すべてを削除します。dismissCoordinatorコーディネーター全体を削除し、それはツリーの関連する子供たちです。
リポジトリをクローンし、 StinsenAppを例/アプリで実行して、 Stinsenの使用方法を感じます。 StinsenAppは、iOS、TVOS、WatchOS、MacOSで動作します。 Stinsenが使用できる機能の多くを紹介しようとします。このREADMEのコードのほとんどは、サンプルアプリからのものです。また、Swiftuiでテスト可能なMVVM-Cアーキテクチャを適用するためにStinsenを使用する方法を示す例もあります。これは、例/MVVMで利用可能です。
@EnvironmentObjectにはView内でのみアクセスできるため、 StinsenはViewModelからいくつかのルーティング方法を提供します。コーディネーターを介してコーディネーターを注入するか、作成時に登録して、依存関係噴射フレームワークを介してビューモデルで解決できます。これらは、最大の制御と機能があるため、これを行う推奨方法です。
他の方法は、 onAppear関数を使用してルーターを渡すことです。
struct TodosScreen : View {
@ StateObject var viewModel = TodosViewModel ( )
@ EnvironmentObject var projects : TodosCoordinator . Router
var body : some View {
List {
/* ... */
}
. onAppear {
viewModel . router = projects
}
}
} RouterStoreと呼ばれるものを使用して、ルーターを再生することもできます。 RouterStoreルーターのインスタンスを保存し、カスタムプロパティワッパーを介して入手できます。
ルーターを取得するには:
class LoginScreenViewModel : ObservableObject {
// directly via the RouterStore
var main : MainCoordinator . Router ? = RouterStore . shared . retrieve ( )
// via the RouterObject property wrapper
@ RouterObject
var unauthenticated : Unauthenticated . Router ?
init ( ) {
}
func loginButtonPressed ( ) {
main ? . root ( . authenticated )
}
func forgotPasswordButtonPressed ( ) {
unauthenticated ? . route ( to : . forgotPassword )
}
}この例を表示するには、MVVM-AppをExamples/MVVMで確認してください。
コーディネーターによって生成されたビューをカスタマイズしたい場合があります。 NavigationCoordinatableとTabCoordinatableには、そうするために実装できるcustomize機能があります。
final class AuthenticatedCoordinator : TabCoordinatable {
/* ... */
@ ViewBuilder func customize ( _ view : AnyView ) -> some View {
view
. onReceive ( Services . shared . $authentication ) { authentication in
switch authentication {
case . authenticated :
self . root ( . authenticated )
case . unauthenticated :
self . root ( . unauthenticated )
}
}
}
}
}また、カスタマイズに使用できるViewWrapperCoordinatorもあります。
コーディネーター/ルーターのほとんどの関数はコーディネーターを返しているため、結果を使用してそれらを連鎖させて、必要に応じてより高度なルーティングを実行できます。たとえば、タブを変更し、ログイン後にアプリのどこからでも特定のTODOを選択するSwiftUIボタンを作成するには:
VStack {
ForEach ( todosStore . favorites ) { todo in
Button ( todo . name ) {
authenticatedRouter
. focusFirst ( . todos )
. child
. popToRoot ( )
. route ( to : . todo , todo . id )
}
}
} authenticatedRouterたAuthenticatedCoordinator TabCoordinatableであるため、関数は次のとおりです。
focusFirst :ルートtodosで表される最初のタブを返し、アクティブなタブを既にアクティブなものでない限り、アクティブなタブにします。child :子供を返し、 Todos -tabはNavigationViewCoordinatorであり、子供はNavigationCoordinatableです。popToRoot :存在していたかもしれないし、そうでないかもしれない子供はすべて飛び出します。route :指定されたIDを使用してルートTodoにルートします。 Stinsenはキーパスを使用してルートを表すため、関数はタイプセーフであり、無効なチェーンを作成できません。これは、 AからB 、 BからCのルートがある場合、最初にBにルーティングせずにAからCからCからCからルーティングを試みる場合、アプリはコンパイルされません。また、 TabCoordinatableなどでpopToRoot()などのアクションを実行することはできません。
返された値を使用すると、アプリ内で簡単にディープリンクできます。
final class MainCoordinator : NavigationCoordinatable {
@ ViewBuilder func customize ( _ view : AnyView ) -> some View {
view . onOpenURL { url in
if let coordinator = self . hasRoot ( . authenticated ) {
do {
// Create a DeepLink-enum
let deepLink = try DeepLink ( url : url , todosStore : coordinator . todosStore )
switch deepLink {
case . todo ( let id ) :
coordinator
. focusFirst ( . todos )
. child
. route ( to : . todo , id )
}
} catch {
print ( error . localizedDescription )
}
}
}
}
} Stinsenには、標準的なSwiftuiビューのためのいくつかのコーディナテーブルが付属しています。たとえば、ハンバーガーメヌに使用したい場合は、独自のものを作成する必要があります。ソースコードをチェックして、インスピレーションを得てください。
Stinsenは、2つのインストール方法、ココアポッドとSPMをサポートしています。
Xcodeとプロジェクトを開き、 File / Swift Packages / Add package dependency...テキストフィールド「パッケージリポジトリURLの入力」で、 https://github.com/rundfunk47/stinsenを書き、次に2回押します
アプリのルートディレクトリにPodfileを作成します。追加
# Podfile
use_frameworks!
target 'YOUR_TARGET_NAME' do
pod 'Stinsen'
end
DoubleColumnNavigationViewStyleをサポートしていません。この理由は、SwiftuiのisActiveの問題のために予想どおりに機能しないためです。回避策: uiviewrepresentableを使用するか、独自の実装を作成します。Byvaでは、100%Swiftuiアプリケーションを作成するよう努めているため、これや他のニーズを満たすコーディネーターフレームワークを作成する必要があるのは当然です。フレームワークは生産で使用され、約50のフローと〜100のスクリーンを管理します。フレームワークは、 @rundfunk47によって維持されます。
スウェーデンの「ステーションマスター」のスウェーデン語は短く、スティンセンは明確な記事「The Station Master」です。口語的には、この用語は、列車のルーティングを担当する列車の派遣者を指すために主に使用されていました。ロゴは、スウェーデンのリンキングの鉄道駅の近くにあるスティンの木製の像に基づいています。
Stinsen V2の最大の変化は、Stinsen V1よりもタイプセーフであることです。これにより、チェーンや深いリンクが容易になります。
AnyCoordinatableプロトコルに置き換えられています。古い任意のAnyCoordinatableと同じ任務を実行せず、バージョン2のよりタイプセーフルーティングには適合しないため、プロジェクトから削除してください。route(to: .a) route(to: .a)を使用します。.view()を使用してください。@Rootを使用し、 .root()を使用してそれらを切り替えて同じ機能を取得します。StinsenはMITライセンスの下でリリースされます。詳細については、ライセンスを参照してください。