UIKitとの連携
チュートリアルより。 UIPageViewControllerをSwiftUIで使う。
まず、使いたいUIViewControllerをwrapするクラスを作る。 そのクラスはUIViewControllerRepresentableを実装する。
import Foundation import SwiftUI import UIKit struct PageViewController: UIViewControllerRepresentable { typealias UIViewControllerType = UIPageViewController var controllers:[UIViewController] // ページ情報 @Binding var currentPage:Int // 関連するdataSorce, delegate等のクラスはCoordinaterとして渡す。 func makeCoordinator() -> Coordinater { Coordinater(self) } // ここでViewControllerを作って渡す。delegate等もcontextを通じてつなげる。 func makeUIViewController(context contetxt:Context) -> UIPageViewController { let pageViewController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: . horizontal) pageViewController.dataSource = contetxt.coordinator pageViewController.delegate = contetxt.coordinator return pageViewController } // 更新 func updateUIViewController(_ pageViewController: UIPageViewController, context: Context) { pageViewController.setViewControllers([controllers[currentPage]], direction: .forward, animated: true) } // DataSorce, Delegate等はCoordinaterとして宣言する。 class Coordinater: NSObject, UIPageViewControllerDataSource,UIPageViewControllerDelegate { var parent:PageViewController init(_ pageViewController:PageViewController) { self.parent = pageViewController } func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { guard let index = parent.controllers.firstIndex(of: viewController) else { return nil } if index == 0 { return parent.controllers.last; } return parent.controllers[index - 1] } func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? { guard let index = parent.controllers.firstIndex(of: viewController) else { return nil } if index + 1 == parent.controllers.count { return parent.controllers.first } return parent.controllers[index + 1] } func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) { if completed, let visibleViewController = pageViewController.viewControllers?.first, let inedx = parent.controllers.firstIndex(of:visibleViewController) { parent.currentPage = inedx } } } }
UIViewも似た感じで扱える。UIViewRepresentableを実装する。
import SwiftUI import UIKit struct PageControl: UIViewRepresentable { var numberOfPages:Int @Binding var currentPage:Int func makeUIView(context:Context) -> UIPageControl { let control = UIPageControl() control.numberOfPages = numberOfPages // controlのtarget, actionをつないでいる。 control.addTarget(context.coordinator, action: #selector(Coordinator.updateCurrentPage(sender:)), for: .valueChanged) return control } // 表示ロジック用のクラスを作る。 func makeCoordinator() -> Coordinator { Coordinator(self) } // 更新 func updateUIView(_ uiView: UIPageControl, context: UIViewRepresentableContext<PageControl>) { uiView.currentPage = currentPage } class Coordinator: NSObject { var control: PageControl init(_ control:PageControl) { self.control = control } @objc func updateCurrentPage(sender:UIPageControl) { control.currentPage = sender.currentPage } } }
これらのプロトコルを実装すれば普通のビューのように扱える。
import SwiftUI struct PageView<Page:View>: View { /// UIPageViewControllerではUIViewController達として扱うのでSwiftUIのViewをUIHostViewControllerでくるんで扱う。 var viewControllers:[UIHostingController<Page>] @State var currentPage = 0 init(_ views:[Page]) { self.viewControllers = views.map { UIHostingController(rootView: $0) } } var body: some View { ZStack(alignment: .bottomTrailing){ PageViewController(controllers: viewControllers, currentPage: $currentPage) PageControl(numberOfPages: viewControllers.count, currentPage: $currentPage) .padding(.trailing) } } } struct PageView_Previews: PreviewProvider { static var previews: some View { PageView(features.map { FeatureCard(landmark: $0)}) .aspectRatio(3/2, contentMode: .fit) } }