0
1

SwiftUIでのページ遷移デザイン

Last updated at Posted at 2023-10-03

SwiftUIで開発の初めにページ遷移の土台を作る機会があったのでこんな感じに作ったので載せて起きます。
作り方の好みもありますので参考程度で。
環境はiOS16で作成しています。

デモ画像

sample.gif

特定の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ではEnvironmentObjectparamを取得して、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)
      }
    }
  }
}

おわりに

いかがだったでしょうか?
もっと自分ならいい感じに作れるといった意見もあったかと思います。
役に立った方は👍いいねを頂ければ幸いです。

サンプルコード

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1