Skip to content

Instantly share code, notes, and snippets.

@denis-obukhov
Created March 29, 2023 16:36
Show Gist options
  • Save denis-obukhov/897d9c5644702ccd39bf1e38eda26600 to your computer and use it in GitHub Desktop.
Save denis-obukhov/897d9c5644702ccd39bf1e38eda26600 to your computer and use it in GitHub Desktop.
SwiftUI UIPageViewController wrapper with data source/lazy loading for Identifiable data
import SwiftUI
import UIKit
struct PageView<Page: View, T: Identifiable>: UIViewControllerRepresentable {
private class ModelViewController: UIHostingController<Page?> {
var id: T.ID?
init(id: T.ID?, rootView: Page?) {
self.id = id
super.init(rootView: rootView)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .clear
}
}
let data: [T]
@Binding var currentID: T.ID?
@ViewBuilder var pageBuilder: (T) -> Page
func makeUIViewController(context: Context) -> UIPageViewController {
let pageViewController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal)
pageViewController.dataSource = context.coordinator
pageViewController.delegate = context.coordinator
let model = data.first(where: { $0.id == currentID })
let hostingController = ModelViewController(id: currentID, rootView: model.map(pageBuilder))
pageViewController.setViewControllers([hostingController], direction: .forward, animated: false)
return pageViewController
}
func updateUIViewController(_ pageViewController: UIPageViewController, context: Context) {
guard
let oldViewController = pageViewController.viewControllers?.first as? ModelViewController,
let oldIndex = data.firstIndex(where: { $0.id == oldViewController.id }),
let newIndex = data.firstIndex(where: { $0.id == currentID })
else { return }
guard oldViewController.id != currentID else {
oldViewController.rootView = pageBuilder(data[newIndex])
return
}
let direction: UIPageViewController.NavigationDirection = (newIndex >= oldIndex) ? .forward : .reverse
let hostingController = ModelViewController(id: currentID, rootView: pageBuilder(data[newIndex]))
pageViewController.setViewControllers([hostingController], direction: direction, animated: true)
}
func makeCoordinator() -> Coordinator { Coordinator(parent: self) }
class Coordinator: NSObject, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
var parent: PageView
init(parent: PageView) {
self.parent = parent
}
func pageViewController(
_ pageViewController: UIPageViewController,
viewControllerBefore viewController: UIViewController
) -> UIViewController? {
guard
let viewController = viewController as? ModelViewController,
let index = parent.data.firstIndex(where: { $0.id == viewController.id }),
index != 0
else {
return nil
}
let model = parent.data[index - 1]
return ModelViewController(id: model.id, rootView: parent.pageBuilder(model))
}
func pageViewController(
_ pageViewController: UIPageViewController,
viewControllerAfter viewController: UIViewController
) -> UIViewController? {
guard
let viewController = viewController as? ModelViewController,
let index = parent.data.firstIndex(where: { $0.id == viewController.id }),
index + 1 != parent.data.count
else {
return nil
}
let model = parent.data[index + 1]
return ModelViewController(id: model.id, rootView: parent.pageBuilder(model))
}
func pageViewController(
_ pageViewController: UIPageViewController,
didFinishAnimating finished: Bool,
previousViewControllers: [UIViewController],
transitionCompleted completed: Bool
) {
guard
completed,
let currentViewController = pageViewController.viewControllers?.first as? ModelViewController
else {
return
}
parent.currentID = currentViewController.id
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment