セクション 103
SwiftUI ビューの状態内でページを追跡する
カスタムの UIPageControl を追加する準備をするには、PageView 内から現在のページを追跡する方法が必要です。
これを行うには、PageView 内で @State プロパティを宣言し、このプロパティへの拘束を PageViewController ビューに渡します。PageViewController は、表示されているページに一致するように拘束を更新します。
ステップ 1
まず、currentPage 拘束 (binding) を PageViewController のプロパティとして追加します。
@Binding プロパティの宣言に加えて、setViewControllers(_:direction:animated:) への呼び出しをも更新し、currentPage 拘束の値を渡します。
    PageViewController.swift
- import SwiftUI
- import UIKit
- struct PageViewController<Page: View>: UIViewControllerRepresentable {
var pages: [Page]
@Binding var currentPage: Int
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIViewController(context: Context) -> UIPageViewController {
let pageViewController = UIPageViewController(
transitionStyle: .scroll,
navigationOrientation: .horizontal)
pageViewController.dataSource = context.coordinator
return pageViewController
}
func updateUIViewController(_ pageViewController: UIPageViewController, context: Context) {
pageViewController.setViewControllers(
[context.coordinator.controllers[currentPage]], direction: .forward, animated: true)
}
class Coordinator: NSObject, UIPageViewControllerDataSource {
var parent: PageViewController
var controllers = [UIViewController]()
init(_ pageViewController: PageViewController) {
parent = pageViewController
controllers = parent.pages.map { UIHostingController(rootView: $0) }
}
func pageViewController(
_ pageViewController: UIPageViewController,
viewControllerBefore viewController: UIViewController) -> UIViewController?
{
guard let index = controllers.firstIndex(of: viewController) else {
return nil
}
if index == 0 {
return controllers.last
}
return controllers[index - 1]
}
func pageViewController(
_ pageViewController: UIPageViewController,
viewControllerAfter viewController: UIViewController) -> UIViewController?
{
guard let index = controllers.firstIndex(of: viewController) else {
return nil
}
if index + 1 == controllers.count {
return controllers.first
}
return controllers[index + 1]
}
}
- }
ステップ 2
PageView 内に @State 変数を宣言し、子の PageViewController を作成するときにプロパティへの結束を渡します。
重要
$ 構文を使用して、状態 (state) として格納される値への結束を作成することを忘れないでください。
    PageView.swift
- import SwiftUI
- struct PageView<Page: View>: View {
var pages: [Page]
@State private var currentPage = 0
var body: some View {
PageViewController(pages: pages, currentPage: $currentPage)
}
- }
- struct PageView_Previews: PreviewProvider {
static var previews: some View {
PageView(pages: ModelData().features.map { FeatureCard(landmark: $0) })
.aspectRatio(3 / 2, contentMode: .fit)
}
- }
プレビューその 1
ステップ 3
その初期値を変更して、値が PageViewController への結束を通過することをテストしてみましょう。
実験
ページビューコントローラを 2 番目のビューに飛ばさせるボタンを PageView に追加します。
    PageView.swift
- struct PageView<Page: View>: View {
var pages: [Page]
@State private var currentPage = 1
var body: some View {
プレビューその 2
ステップ 4
currentPage プロパティを使用してテキストビューを追加し、@State プロパティの値を監視できるようにします。
ページからページとスワイプしても、値は変わらないことに注意してください。
    PageView.swift
- struct PageView<Page: View>: View {
var pages: [Page]
@State private var currentPage = 0
var body: some View {
VStack {
PageViewController(pages: pages, currentPage: $currentPage)
Text("Current Page: \(currentPage)")
}
}
- }
- struct PageView_Previews: PreviewProvider {
static var previews: some View {
PageView(pages: ModelData().features.map { FeatureCard(landmark: $0) })
.aspectRatio(3 / 2, contentMode: .fit)
}
- }
プレビューその 3
ステップ 5
PageViewController.swift 内で、コーディネータを UIPageViewControllerDelegate に準拠させ、 pageViewController(_:didFinishAnimating:
previousViewControllers:transitionCompleted complete:Bool) メソッドを追加します。
SwiftUI は、ページ切り替えアニメーションが完了するたびにこのメソッドを呼び出すため、現在のビューコントローラのインデックスを見つけて、拘束を更新できます。
    PageViewController.swift
[context.coordinator.controllers[currentPage]], direction: .forward, animated: true)
}
class Coordinator: NSObject, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
var parent: PageViewController
var controllers = [UIViewController]()
return controllers[index + 1]
}
func pageViewController(
_ pageViewController: UIPageViewController,
didFinishAnimating finished: Bool,
previousViewControllers: [UIViewController],
transitionCompleted completed: Bool) {
if completed,
let visibleViewController = pageViewController.viewControllers?.first,
let index = controllers.firstIndex(of: visibleViewController) {
parent.currentPage = index
}
}
}
- }
ステップ 6
データソースに加えて、UIPageViewController のデリゲートとしてコーディネータを割り当てます。
拘束 (binding) が両方向に接続されているので、テキストビューが更新され、スワイプするたびに正しいページ番号が表示されます。
    PageViewController.swift
navigationOrientation: .horizontal)
pageViewController.dataSource = context.coordinator
pageViewController.delegate = context.coordinator
return pageViewController
プログラム全体