更新 @EnvironmentObject var 以将数据传递给 SwiftUI 中的 PageViewController 会导致 ViewController 之间的滑动丢失
Updating an @EnvironmentObject var to pass data to PageViewController in SwiftUI results in loss of swiping between ViewControllers
在我的 SwiftUI 应用程序中,我目前有一个使用 UIKit 实现的页面ViewController。它遵循传统的 SwiftUI - Apple 的 SwiftUI UIKit 集成 tutorials.
中概述的 UIKit 实现
我有填充 UIViewController 的数据,该数据位于传递给 PageViewController 的控制器数组中,由 @Environment
变量提供。在应用程序的另一个屏幕中,您可以发出导致环境对象更新的操作,触发重新呈现位于 PageViewController.
中的 ViewController
但是,此重新呈现会导致问题,因为 ViewController 是使用新标识符重新制作的,因此无法在 [=13= 中找到 viewController 的索引] 数组在 Class 协调器页面 ViewController 函数中。这会导致索引默认为 nil 并禁用更新后的 viewController 上的任何滑动。我仍然可以使用页面控制点在 viewController 之间导航,但我想确定如何更新 parent.controller 数组以包含新的 ViewController 并丢弃旧的.
经过数小时的搜索、调试和修补,我一直无法找到一种方法来使用新的 ViewController 替换已丢弃的旧视图来重置父控制器数组。下面是 PageViewController.
的代码
import SwiftUI
import UIKit
struct PageViewController: UIViewControllerRepresentable {
var controllers: [UIViewController]
@Binding var currentPage: Int
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIViewController(context: Context) -> UIPageViewController {
let pageViewController = UIPageViewController(
transitionStyle: .scroll,
navigationOrientation: .horizontal)
pageViewController.view.backgroundColor = UIColor.clear
pageViewController.dataSource = context.coordinator
pageViewController.delegate = context.coordinator
return pageViewController
}
func updateUIViewController(_ pageViewController: UIPageViewController, context: Context) {
pageViewController.setViewControllers(
[controllers[currentPage]], direction: .forward, animated: false)
}
class Coordinator: NSObject, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
var parent: PageViewController
init(_ pageViewController: PageViewController) {
self.parent = pageViewController
}
func pageViewController(
_ pageViewController: UIPageViewController,
viewControllerBefore viewController: UIViewController) -> UIViewController? {
print(parent.controllers)
print(viewController)
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 index = parent.controllers.firstIndex(of: visibleViewController) {
parent.currentPage = index
}
}
}
}
更新填充给定数据的环境状态后 ViewController,导致重新渲染,协调器 class 可以打印包含旧 [=30= 的控制器数组] 以及下面注释代码中的新 ViewController 但我还没有找到可靠的方法来确保新的 ViewController 有效地替换旧的
struct PageViewController: UIViewControllerRepresentable {
...
class Coordinator: NSObject, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
...
func pageViewController(
_ pageViewController: UIPageViewController,
viewControllerBefore viewController: UIViewController) -> UIViewController? {
print(parent.controllers)
// prints out array of ViewControllers including old ViewController that has now been
// discarded
// [<_TtGC7SwiftUI19UIHostingControllerVS_7AnyView_: 0x7fd595c93de0>,
// <_TtGC7SwiftUI19UIHostingControllerVS_7AnyView_: 0x7fd595c94be0>,
// <_TtGC7SwiftUI19UIHostingControllerVS_7AnyView_: 0x7fd595c96830>,
// <_TtGC7SwiftUI19UIHostingControllerVS_7AnyView_: 0x7fd595c976b0>]
print(viewController)
// prints out new ViewController that does not exist within the parent.controllers array
// and hence nil is returned from the guard
// <_TtGC7SwiftUI19UIHostingControllerVS_7AnyView_: 0x7fd593721710>
guard let index = parent.controllers.firstIndex(of: viewController) else {
return nil
}
if index == 0 {
return parent.controllers.last
}
return parent.controllers[index - 1]
}
...
}
非常感谢有关此问题的任何帮助或指导!
遇到同样的问题,最后几个小时,我找到了解决方法。
看来是个bug。如果您查看 UIViewControllerRepresentable 循环,它应该从 Init > Coordinator > makeUIViewController > updateUIViewController 开始。
但是如果 PageViewController 中的页面得到更新(我们的案例)。页面由 SwiftUI 重新创建,很好,但是 PageViewController 从 Init > updateUIViewController 得到一个内部错误的创建周期。没有 makeUIViewController 既没有 Coordinator。以某种方式创建了一个新的 UIPageViewController(如何?)导致您注意到的差异。
要解决此问题并强制重新创建 PageViewController,请在您的页面视图代码中添加 .id(UUID),例如:
struct SGPageView<Page: View>: View {
var viewControllers: [UIHostingController<Page>]
@Binding var currentPage:Int
init(_ views: [Page], currentPage:Binding<Int>) {
self.viewControllers = views.map { UIHostingController(rootView: [=10=]) }
self._currentPage = currentPage
}
var body: some View {
PageViewController(controllers: viewControllers, currentPage: $currentPage).id(UUID())
}
}
它会起作用,但您会注意到页面的滚动位置已重置。另一个错误 ;)
认为问题出在协调器上:
当您在 pageviewcontroller 中更新控制器时,Coordinator 的 parent
属性 保持不变(因为它是一个结构,所以他的所有属性)。所以你可以简单地在你的 updateUIViewController
方法中添加这行代码:
context.coordinator.parent = self
另外,记住pageViewController.setViewControllers
的动画会出现,所以你要设置animated为false,或者妥善处理。
还有很多其他方法可以解决这个问题(我写了最直观的解决方案),
重要的是要知道错误来自哪里。
在我的 SwiftUI 应用程序中,我目前有一个使用 UIKit 实现的页面ViewController。它遵循传统的 SwiftUI - Apple 的 SwiftUI UIKit 集成 tutorials.
中概述的 UIKit 实现我有填充 UIViewController 的数据,该数据位于传递给 PageViewController 的控制器数组中,由 @Environment
变量提供。在应用程序的另一个屏幕中,您可以发出导致环境对象更新的操作,触发重新呈现位于 PageViewController.
但是,此重新呈现会导致问题,因为 ViewController 是使用新标识符重新制作的,因此无法在 [=13= 中找到 viewController 的索引] 数组在 Class 协调器页面 ViewController 函数中。这会导致索引默认为 nil 并禁用更新后的 viewController 上的任何滑动。我仍然可以使用页面控制点在 viewController 之间导航,但我想确定如何更新 parent.controller 数组以包含新的 ViewController 并丢弃旧的.
经过数小时的搜索、调试和修补,我一直无法找到一种方法来使用新的 ViewController 替换已丢弃的旧视图来重置父控制器数组。下面是 PageViewController.
的代码import SwiftUI
import UIKit
struct PageViewController: UIViewControllerRepresentable {
var controllers: [UIViewController]
@Binding var currentPage: Int
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIViewController(context: Context) -> UIPageViewController {
let pageViewController = UIPageViewController(
transitionStyle: .scroll,
navigationOrientation: .horizontal)
pageViewController.view.backgroundColor = UIColor.clear
pageViewController.dataSource = context.coordinator
pageViewController.delegate = context.coordinator
return pageViewController
}
func updateUIViewController(_ pageViewController: UIPageViewController, context: Context) {
pageViewController.setViewControllers(
[controllers[currentPage]], direction: .forward, animated: false)
}
class Coordinator: NSObject, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
var parent: PageViewController
init(_ pageViewController: PageViewController) {
self.parent = pageViewController
}
func pageViewController(
_ pageViewController: UIPageViewController,
viewControllerBefore viewController: UIViewController) -> UIViewController? {
print(parent.controllers)
print(viewController)
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 index = parent.controllers.firstIndex(of: visibleViewController) {
parent.currentPage = index
}
}
}
}
更新填充给定数据的环境状态后 ViewController,导致重新渲染,协调器 class 可以打印包含旧 [=30= 的控制器数组] 以及下面注释代码中的新 ViewController 但我还没有找到可靠的方法来确保新的 ViewController 有效地替换旧的
struct PageViewController: UIViewControllerRepresentable {
...
class Coordinator: NSObject, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
...
func pageViewController(
_ pageViewController: UIPageViewController,
viewControllerBefore viewController: UIViewController) -> UIViewController? {
print(parent.controllers)
// prints out array of ViewControllers including old ViewController that has now been
// discarded
// [<_TtGC7SwiftUI19UIHostingControllerVS_7AnyView_: 0x7fd595c93de0>,
// <_TtGC7SwiftUI19UIHostingControllerVS_7AnyView_: 0x7fd595c94be0>,
// <_TtGC7SwiftUI19UIHostingControllerVS_7AnyView_: 0x7fd595c96830>,
// <_TtGC7SwiftUI19UIHostingControllerVS_7AnyView_: 0x7fd595c976b0>]
print(viewController)
// prints out new ViewController that does not exist within the parent.controllers array
// and hence nil is returned from the guard
// <_TtGC7SwiftUI19UIHostingControllerVS_7AnyView_: 0x7fd593721710>
guard let index = parent.controllers.firstIndex(of: viewController) else {
return nil
}
if index == 0 {
return parent.controllers.last
}
return parent.controllers[index - 1]
}
...
}
非常感谢有关此问题的任何帮助或指导!
遇到同样的问题,最后几个小时,我找到了解决方法。
看来是个bug。如果您查看 UIViewControllerRepresentable 循环,它应该从 Init > Coordinator > makeUIViewController > updateUIViewController 开始。
但是如果 PageViewController 中的页面得到更新(我们的案例)。页面由 SwiftUI 重新创建,很好,但是 PageViewController 从 Init > updateUIViewController 得到一个内部错误的创建周期。没有 makeUIViewController 既没有 Coordinator。以某种方式创建了一个新的 UIPageViewController(如何?)导致您注意到的差异。
要解决此问题并强制重新创建 PageViewController,请在您的页面视图代码中添加 .id(UUID),例如:
struct SGPageView<Page: View>: View {
var viewControllers: [UIHostingController<Page>]
@Binding var currentPage:Int
init(_ views: [Page], currentPage:Binding<Int>) {
self.viewControllers = views.map { UIHostingController(rootView: [=10=]) }
self._currentPage = currentPage
}
var body: some View {
PageViewController(controllers: viewControllers, currentPage: $currentPage).id(UUID())
}
}
它会起作用,但您会注意到页面的滚动位置已重置。另一个错误 ;)
认为问题出在协调器上:
当您在 pageviewcontroller 中更新控制器时,Coordinator 的 parent
属性 保持不变(因为它是一个结构,所以他的所有属性)。所以你可以简单地在你的 updateUIViewController
方法中添加这行代码:
context.coordinator.parent = self
另外,记住pageViewController.setViewControllers
的动画会出现,所以你要设置animated为false,或者妥善处理。
还有很多其他方法可以解决这个问题(我写了最直观的解决方案), 重要的是要知道错误来自哪里。