问题
I was following the Apple tutorial for SwiftUI and under 'Interfacing with UIKit' (https://developer.apple.com/tutorials/swiftui/interfacing-with-uikit) we were made to make a UIPageController.
In the tutorial, they used a State property to update the currentPage
variable in PageView
. I wanted to experiment a little bit and decided that I want to embed the PageView
in a different view and have a currentPage
property update in that parent view instead. So, naturally, I changed the @State
property to an @Binding
property in PageView
in order to update currentPage
in the parent.
However, when I did that, the UIPageController stopped working and kept returning nil
from pageViewController(_:viewControllerBefore:) -> UIViewController?
on the second swipe.
This is the code, I'll add comments to make it less tedious to understand:
struct ContentView: View {
@State var currentPage = 0 // This is the new @State variable which should get updated.
var body: some View {
VStack {
PageView([Text("First page"), Text("Second Page"), Text("Third Page")],
currentPage: self.$currentPage) // Passed a binding to the State variable since I want that to be updated when the page changes.
Text("Page number: \(currentPage)") // Removing this fixes the problem, but I need it here.
}
}
}
struct PageView<Page: View>: View {
var viewControllers: [UIHostingController<Page>]
@Binding var currentPage: Int // This used to be an @State property, but I changed it to a binding since the currentPage variable is now being updated elsewhere (In the view above).
init(_ views: [Page], currentPage: Binding<Int>) {
self.viewControllers = views.map { UIHostingController(rootView: $0) } // Simply wrapping all the views inside ViewControllers for UIPageViewController to use.
self._currentPage = currentPage // Assigning the binding.
}
var body: some View {
PageViewController(controllers: self.viewControllers, currentPage: self.$currentPage)
}
}
struct PageViewController: UIViewControllerRepresentable {
var controllers: [UIViewController]
@Binding var currentPage: Int
func makeCoordinator() -> Coordinator { // Makes the coordinator, irrelevant to the problem
Coordinator(self)
}
func makeUIViewController(context: Context) -> UIPageViewController {
// I don't think the problem lies here.
let pageViewController = UIPageViewController(
transitionStyle: .scroll,
navigationOrientation: .horizontal)
pageViewController.dataSource = context.coordinator
pageViewController.delegate = context.coordinator
return pageViewController
}
func updateUIViewController(_ pageViewController: UIPageViewController, context: Context) {
// I don't think the problem lies here.
pageViewController.setViewControllers(
[self.controllers[currentPage]], direction: .forward, animated: true)
}
class Coordinator: NSObject, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
var parent: PageViewController
init(_ pageViewController: PageViewController) {
self.parent = pageViewController
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
// This is where I think the problem lies. When the line below is evaluated, nil is returned on the second swipe when it shouldn't be. It works on the first swipe but on the second swipe nil is returned and therefore nothing is presented.
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 == parent.controllers.count - 1 {
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 index = parent.controllers.firstIndex(of: visibleViewController) {
parent.currentPage = index
}
}
}
}
Also, removing the Text
from ContentView
fixes the issue but I need it there - so removing it is not an option.
If someone can explain how to fix my issue I'll be extremely grateful. No need to provide a code sample if you're too lazy, a simple explanation should also get the job done.
Thanks in advance!
回答1:
The PageView
is recreated during update of body
when Text
is refreshed. The solution to avoid this is to make PageView
confirm to Equatable
.
The simplest demo of fix is as follows (only modified parts). Tested with Xcode 11.4 / iOS 13.4
Note: used same PageView, because PageView2 is not provided
// ...
var body: some View {
VStack {
PageView([Text("First page"), Text("Second Page"), Text("Third Page")],
currentPage: self.$currentPage)
.equatable() // << here !!
Text("Page number: \(currentPage)")
}
}
struct PageView<Page: View>: View, Equatable { // << confirm !!
static func == (lhs: PageView<Page>, rhs: PageView<Page>) -> Bool {
return true // just for demo, make it meaningful on some property
}
// .. other code goes here
来源:https://stackoverflow.com/questions/62262668/uipageviewcontroller-stops-working-after-1-swipe-in-swiftui