Skip to content

Instantly share code, notes, and snippets.

@yosshi4486
Created February 16, 2025 07:24
Show Gist options
  • Save yosshi4486/cad596cb23f201ee5084d416156b0284 to your computer and use it in GitHub Desktop.
Save yosshi4486/cad596cb23f201ee5084d416156b0284 to your computer and use it in GitHub Desktop.
Simple UIPageViewController Bridge
import SwiftUI
import UIKit
// Add refinements to this official practice:
// https://developer.apple.com/tutorials/swiftui/interfacing-with-uikit
struct PageView<Page : View> : View {
var pages: () -> [Page]
@State private var currentIndex: Int = 0
init(@PagesBuilder<Page> pages: @escaping () -> [Page]) {
self.pages = pages
}
var body: some View {
ZStack {
PageViewController(currentIndex: $currentIndex, pages: pages).ignoresSafeArea(edges: .bottom)
VStack {
Spacer()
PageControl(numberOfPages: pages().count, currentPage: $currentIndex)
}
}
}
}
@resultBuilder
struct PagesBuilder<Page : View> {
public static func buildBlock(_ components: Page...) -> [Page] {
components
}
}
struct PageViewController<Page : View> : UIViewControllerRepresentable {
var pages: () -> [Page]
@Binding var currentIndex: Int
init(currentIndex: Binding<Int>, @PagesBuilder<Page> pages: @escaping () -> [Page]) {
self._currentIndex = currentIndex
self.pages = pages
}
func makeUIViewController(context: Context) -> UIPageViewController {
let viewController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal)
viewController.dataSource = context.coordinator
viewController.delegate = context.coordinator
return viewController
}
func updateUIViewController(_ uiViewController: UIPageViewController, context: Context) {
uiViewController.setViewControllers([context.coordinator.controllers[currentIndex]], direction: .forward, animated: false)
}
func makeCoordinator() -> Coordinator {
return Coordinator(parent: self)
}
class Coordinator: NSObject, UIPageViewControllerDelegate, UIPageViewControllerDataSource {
var parent: PageViewController
var controllers: [UIViewController] = []
init(parent: PageViewController) {
self.parent = parent
self.controllers = parent.pages().map({ UIHostingController(rootView: $0) })
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let index = controllers.firstIndex(of: viewController), index != 0 else {
return nil
}
return controllers[index - 1]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let index = controllers.firstIndex(of: viewController), index != (controllers.endIndex - 1) else {
return nil
}
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.currentIndex = index
}
}
}
}
struct PageControl: UIViewRepresentable {
var numberOfPages: Int
@Binding var currentPage: Int
func makeUIView(context: Context) -> UIPageControl {
let control = UIPageControl()
control.numberOfPages = numberOfPages
control.backgroundStyle = .prominent
control.addTarget(context.coordinator, action: #selector(Coordinator.updateCurrentPage(_:)), for: .valueChanged)
return control
}
func updateUIView(_ uiView: UIPageControl, context: Context) {
uiView.currentPage = currentPage
}
func makeCoordinator() -> Coordinator {
return Coordinator(parent: self)
}
class Coordinator: NSObject {
var parent: PageControl
init(parent: PageControl) {
self.parent = parent
}
@MainActor @objc func updateCurrentPage(_ sender: UIPageControl) {
self.parent.currentPage = sender.currentPage
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment