SwiftUIで開発の初めにページ遷移の土台を作る機会があったのでこんな感じに作ったので載せて起きます。
作り方の好みもありますので参考程度で。
環境はiOS16で作成しています。
デモ画像
特定のViewを開いて開発する
Previewで実装と絡めた開発をしていくのにも限界があります。
特に毎回画面遷移して作業するとトライ&エラーの効率が悪く、特定のViewを開いて開発をしたいことが多く出てきます。
そういった事情から特定のViewを指定してシュミレータで作業できるように作りました。
コードの上部分のdestination
で指定して開きます。
var destination: Destination? = nil
//var destination: Destination? = .TodoListView
//var destination: Destination? = .TodoDetailView(Todo(id: 5, value: "hoge"))
画面遷移するViewを定義する
手間ではありますが画面遷移で利用するViewを継ぎ足してやる必要があります。
enumの定義に加えて、SelectDistinationView
の分岐を増やす構造としました。
enum Destination: Hashable {
case IndexView
case TodoListView
case TodoDetailView(Todo)
}
@main
struct RoutingExampleApp: App {
@ObservedObject var param = Param()
var body: some Scene {
WindowGroup {
SelectView()
.environmentObject(param)
}
}
func SelectView() -> AnyView {
return AnyView (
NavigationStack(path: $param.path) {
SelectDistinationView(destination)
.navigationDestination(for: Destination.self) { value in
LazyView(SelectDistinationView(value))
}
}
)
}
func SelectDistinationView(_ destination: Destination?) -> AnyView {
return AnyView(
Group {
switch destination {
case .IndexView: IndexView()
case .TodoListView: TodoListView()
case .TodoDetailView(let todo): TodoDetailView(todo: todo)
default:
LazyView(IndexView())
}
}
)
}
}
ページの引数をどう扱うか
大体の引数はObservableObjectで引き回して使いました。
セッションのように一時的なUser情報等を放り込んだりするのに便利です。
ここではページ情報(Destination
)の引数だけ設定しています。
class Param: ObservableObject {
@Published var path = [Destination]()
}
IndexViewではEnvironmentObject
でparam
を取得して、param.path.append(.TodoListView)
で画面遷移としています。
struct IndexView: View {
@EnvironmentObject var param: Param
var body: some View {
VStack {
Button {
param.path.append(.TodoListView)
} label: {
Text("TodoList")
}
.buttonStyle(.borderedProminent)
}
.padding()
}
}
リストから詳細に値を渡す
次に、ToDoListの場合は詳細ページに引数を渡す必要があります。
試したところNSObject
のclassであれば簡単に引き回せました。RealmでもObject
(中身はNSObject)であれば問題ないようです。
ここではparam.path.append
ではなく、NavigationLink
を使用し、enumに指定したDestination
に値を渡して遷移しています。
挙動としてはappend
するのと何ら変わりません。
final class Todo: NSObject, Identifiable {
var id: Int
var value = ""
var date = Date()
init(id: Int, value: String) {
self.id = id
self.value = value
}
}
struct TodoListView: View {
@EnvironmentObject var param: Param
var todoList = [
Todo(id: 1, value: "Hello"),
Todo(id: 2, value: "Hello"),
Todo(id: 3, value: "Hello"),
Todo(id: 4, value: "Hello"),
Todo(id: 5, value: "Hello"),
]
var body: some View {
VStack {
List {
ForEach(todoList){ todo in
NavigationLink(value: Destination.TodoDetailView(todo)){
Text("\(todo.value) \(todo.id)" )
}
}
}
}
.padding()
}
}
struct TodoDetailView: View {
@Environment(\.dismiss) var dismiss
var todo: Todo
var body: some View {
VStack {
Text("\(todo.value) \(todo.id)" )
}
.toolbar {}
}
}
ルートページへの戻り方
ちなみにですが、ルートページに戻る際は、param.path.removeLast(param.path.count)
で戻れます。
struct TodoDetailView: View {
@EnvironmentObject var param: Param
var todo: Todo
var body: some View {
VStack {
Text("\(todo.value) \(todo.id)" )
}
.toolbar {
ToolbarItem(placement: .bottomBar) {
Button(action: {
param.path.removeLast(param.path.count)
}) {
Text("最初に戻る")
.fontWeight(.regular).font(.title)
}
.buttonStyle(.borderedProminent)
}
}
}
}
おわりに
いかがだったでしょうか?
もっと自分ならいい感じに作れるといった意見もあったかと思います。
役に立った方は👍いいねを頂ければ幸いです。
サンプルコード