🌵

SwiftUIのViewをパスからレンダリングするOSSを作った

2022/07/21に公開

https://github.com/niaeashes/Echeveria

Echeveria といいます。エケベリアと読みます。

類似する OSS に SwiftUI Router などがあります。できるだけシンプルになるように実装したので、プロダクトに導入するには機能不足というケースもあるかもしれません。こういう機能があれば使えるのに、という人は Issue を書いてもらえるとありがたいです。

導入は SwiftPM でのみ可能です。CocoaPods や Carthage で使いたいという方がいれば、PR を書いてください。

何をする Package なの?

パスの文字列を RouteView(path: "/path/to/anywhere") のように渡すと、適切な View をレンダリングしてくれます。

NavigationView などと組み合わせると、遷移先を Path で記述することができます。

struct RootView: View {
    var body: some View {
        NavigationLink(destination: RouteView(path: "/goto")) {
	    Label("Go To")
	}
    }
}

こういうイメージになります。

  • パスに埋め込まれたパラメータを処理できる
  • NotFound 時の View を設定できる
  • RouteView で普通にレンダリングするため、SwiftUI の標準の画面遷移に乗っかることができる
  • UIKit とも悪くない感じに組み合わせられると思う(試してない)

なぜパスベースで View をレンダリングしたいの?

Web と iOS アプリの両方が存在するプロダクトにおいて、Web の特定のページを表現するURLのパス部分をそのまま iOS アプリのルーティングに流用できると、いくつかのメリットがあります。

  • APIサーバーが提供する「そのリソースを表現するためのパス」を Web でも iOS でも変わらず利用できる。
  • DeepLink の解決が楽になるかもしれない(まだ試していないが、多分楽になると思う)
  • ユーザーが書き込んだ Web の URL を、アプリ内部で処理して適切なページにルーティングできる

また、パスベースのルーティングによって以下のメリットもあるかもなと思っています。

  • Push 通知を開封したときにユーザーに表示したいページを統一的に記述できるかもしれない(Push 通知のペイロードに path: "/xxxx" のようなデータを入れて、単純にアプリでそのパスを開く)
  • デバッグ用に「任意のパスを入力したらそれをそのまま開ける仕組み」を簡単に導入できる

デメリットとか注意点

Echeveria ではルーティングというか実際の画面遷移処理は SwiftUI の標準的な画面遷移を利用することが前提になっています。最初は既存の画面遷移の仕組みをすべて置き換えるようなものを考えていたのですが、Sheet とかの扱いがめんどくさすぎて諦めました。

もし自前で何らかの画面遷移を管理する仕組みを実装していても、パスから View をレンダリングする部分だけを Echeveria が担当できるので、うまく組み合わせられると考えています。

極単純なデメリットとして、パスを文字列として扱っているため、ビルド時に型の恩恵が受けられません。パスを型ベースで記述する方法は、実際にはパスの管理が大変になる割にメリットが薄いため、採用しませんでした。この点をストレスと感じるかどうかはプロジェクトやエンジニアによって様々でしょう。

仕組みの話

コード量が少ないので読んでもらうほうが早そうです。

AnyView

RouteView は内部で AnyView を使っています。AnyView は扱いに気をつけないとパフォーマンス上の課題が出ると言われたりします。

AnyView にパフォーマンス上の課題が出るのは、AnyView の子全体が再レンダリングされるタイミングが存在するからです。しかし、RouteView はパス文字列にのみ依存しているため、再レンダリングが走る機会はほぼありません。詳細は省きますが、パフォーマンス上の課題は多分そこまでないです。

AnyView のパフォーマンスについては https://nalexn.github.io/anyview-vs-group/ が非常に参考になりました。

ベンチマークやプロファイルを精査したわけではありませんので、そのうち時間を見つけてやろうと思っています。

@resultBuilder

SwiftUI を実現するための Swift の機能に @resultBuilder があります。Echeveria も @resultBuilder を使って Router を定義できるようにしています。@resultBuilder は非常に便利なので、自分でよく作っています。

@resultBuilder については https://github.com/apple/swift-evolution/blob/main/proposals/0289-result-builders.md がわかりやすいですが、これはあくまで provosal なので、細かい部分は実際と異なるかもしれません。

その他

英語で README を書いてますが、英語にはあまり自信がないので、おかしい表現があれば PR をもらえると助かります。

Echeveria は実際のプロダクトに導入してリリースもしています。ほとんど既存の View を破壊することなく、もともとあった画面遷移の仕組みを SwiftUI Standard + Echeveria に置き換えられたので、作ってよかったと思っています。

現在 RouteView のパス文字列の変更に伴ってトランジションを走らせるような仕組みはありません。今後サポートするかどうかも未定です。個人的には過剰な機能だと思っています。もしそういった機能がほしいのであれば、SwiftUI Router を利用するのが良さそうです。(できそうなことは書いてありましたが、試してはいません)

自分で必要なものを作るというのは割と好きなのですが、OSSとして公開して記事まで書くのは初めてで、面白かったです。やってみなければわからないことも色々ありました。

Discussion