Created
March 29, 2023 16:36
-
-
Save denis-obukhov/897d9c5644702ccd39bf1e38eda26600 to your computer and use it in GitHub Desktop.
SwiftUI UIPageViewController wrapper with data source/lazy loading for Identifiable data
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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