从SwiftUI使用UIPageView时,同一页面连续显示两次的问题

iovurdzv  于 2023-11-16  发布在  Swift
关注(0)|答案(1)|浏览(152)

分页是通过SwiftUI中的UIPageView实现的。我遇到了一个问题,在页面滑动时,同一个页面连续显示两次。导致这种现象的条件是翻页的速度。这个问题在缓慢或非常快的翻页时不会出现,但在中速翻页时会出现。Here is a video of this phenomenon.
复制的完整代码附在下面。您的帮助将不胜感激。提前感谢您。

  1. struct ContentView: View {
  2. @State var currentPage: Int = 2
  3. var body: some View {
  4. VStack {
  5. Text(currentPage.description)
  6. .font(.largeTitle)
  7. PageView(
  8. pages: Array(0...100).map({ num in
  9. Text("Page of \(num)")
  10. .font(.largeTitle)
  11. .padding(100)
  12. .background(Color(.systemOrange))
  13. .cornerRadius(10)
  14. }),
  15. currentPage: $currentPage)
  16. }
  17. }
  18. }
  19. struct PageView<Page: View>: UIViewControllerRepresentable {
  20. var pages: [Page]
  21. @Binding var currentPage: Int
  22. func makeUIViewController(context: Context) -> UIPageViewController {
  23. let pageViewController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: [UIPageViewController.OptionsKey.interPageSpacing: 30])
  24. pageViewController.dataSource = context.coordinator
  25. pageViewController.delegate = context.coordinator
  26. return pageViewController
  27. }
  28. func updateUIViewController(_ pageViewController: UIPageViewController, context: Context) {
  29. context.coordinator.controllers = pages.map({
  30. let hostingController = UIHostingController(rootView: $0)
  31. hostingController.view.backgroundColor = .clear
  32. return hostingController
  33. })
  34. let currentViewController = context.coordinator.controllers[currentPage]
  35. pageViewController.setViewControllers([currentViewController], direction: .forward, animated: false)
  36. }
  37. func makeCoordinator() -> Coordinator {
  38. return Coordinator(parent: self, pages: pages)
  39. }
  40. class Coordinator: NSObject, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
  41. var parent: PageView
  42. var controllers: [UIViewController]
  43. init(parent: PageView, pages: [Page]) {
  44. self.parent = parent
  45. self.controllers = pages.map({
  46. let hostingController = UIHostingController(rootView: $0)
  47. hostingController.view.backgroundColor = .clear
  48. return hostingController
  49. })
  50. }
  51. func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
  52. guard let index = controllers.firstIndex(of: viewController) else {
  53. return controllers[parent.currentPage - 1]
  54. }
  55. if index == 0 {
  56. return nil
  57. }
  58. return controllers[index - 1]
  59. }
  60. func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
  61. guard let index = controllers.firstIndex(of: viewController) else {
  62. return controllers[parent.currentPage + 1]
  63. }
  64. if index + 1 == controllers.count {
  65. return nil
  66. }
  67. return controllers[index + 1]
  68. }
  69. func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
  70. if completed,
  71. let currentViewController = pageViewController.viewControllers?.first,
  72. let currentIndex = controllers.firstIndex(of: currentViewController)
  73. {
  74. parent.currentPage = currentIndex
  75. }
  76. }
  77. }
  78. }

字符串
我们也尝试过从TabView、ScrollView等SwiftUI中实现分页,但我想显示的屏幕非常重,使用UIKit的UIPageView的方法是唯一一个几乎没有屏幕蚕食的方法。

jm2pwxwz

jm2pwxwz1#

didFinishAnimating中,只有在controllers中找到当前视图控制器时才更新currentPage

  1. if completed,
  2. let currentViewController = pageViewController.viewControllers?.first,
  3. let currentIndex = controllers.firstIndex(of: currentViewController)
  4. {
  5. parent.currentPage = currentIndex
  6. }

字符串
考虑一下当你设置parent.currentPage-updateUIViewController将被调用时会发生什么,你给予一个全新的VC数组给协调器。如果此时didFinishAnimating被再次调用,currentViewController仍然包含旧的示例。它不会在controllers中找到currentViewController,并且无法设置currentPage
请注意,setViewControllers仅在动画完成后更改pageViewController.viewControllers
因此,要解决这个问题,只需在每次视图更新时不给予新的UIHostingController s即可:

  1. func updateUIViewController(_ pageViewController: UIPageViewController, context: Context) {
  2. let currentViewController = context.coordinator.controllers[currentPage]
  3. pageViewController.setViewControllers([currentViewController], direction: .forward, animated: false)
  4. }


如果你有一个恒定的页面集,这就可以工作。否则,这意味着当pages改变时,你将无法更新视图控制器。
更好的解决方案是使用面向数据的方法,就像所有其他SwiftUI视图一样。
这个想法是PageView应该接受一个 data 的列表,而不是View s的列表。它还需要一个将数据转换为View s的函数。
协调器还使用 data。在数据数组上使用firstIndex(of:),并且仅在必要时创建VC。
下面是一个粗略的草图。它可以工作,但它可能可以通过使用IdentifiableDictionary来优化。如果数据没有改变,makeVC制作的VC也可以重用。

  1. struct PageView<Data: Equatable, Page: View>: UIViewControllerRepresentable {
  2. let pages: [Data]
  3. @Binding var currentPage: Int
  4. @ViewBuilder let pageView: (Data) -> Page
  5. func makeUIViewController(context: Context) -> UIPageViewController {
  6. let pageViewController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: [UIPageViewController.OptionsKey.interPageSpacing: 30])
  7. pageViewController.dataSource = context.coordinator
  8. pageViewController.delegate = context.coordinator
  9. return pageViewController
  10. }
  11. func updateUIViewController(_ pageViewController: UIPageViewController, context: Context) {
  12. context.coordinator.pages = pages
  13. context.coordinator.pageView = pageView
  14. let currentViewController = context.coordinator.makeVC(page: pages[currentPage])
  15. pageViewController.setViewControllers([currentViewController], direction: .forward, animated: false)
  16. context.coordinator.pageDidChange = { currentPage = $0 }
  17. }
  18. func makeCoordinator() -> Coordinator {
  19. return Coordinator(pages: pages, pageView: pageView)
  20. }
  21. class PageHostingController: UIHostingController<Page> {
  22. let data: Data
  23. init(rootView: Page, data: Data) {
  24. self.data = data
  25. super.init(rootView: rootView)
  26. }
  27. @MainActor required dynamic init?(coder aDecoder: NSCoder) {
  28. fatalError("init(coder:) has not been implemented")
  29. }
  30. }
  31. class Coordinator: NSObject, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
  32. var pageDidChange: ((Int) -> Void)?
  33. var pages: [Data]
  34. var pageView: (Data) -> Page
  35. init(pages: [Data], @ViewBuilder pageView: @escaping (Data) -> Page) {
  36. self.pages = pages
  37. self.pageView = pageView
  38. }
  39. func makeVC(page: Data) -> PageHostingController {
  40. let hostingController = PageHostingController(rootView: pageView(page), data: page)
  41. hostingController.view.backgroundColor = .clear
  42. return hostingController
  43. }
  44. func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
  45. guard let pageHost = viewController as? PageHostingController,
  46. let index = pages.firstIndex(of: pageHost.data) else {
  47. return nil
  48. }
  49. if index == 0 {
  50. return nil
  51. }
  52. return makeVC(page: pages[index - 1])
  53. }
  54. func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
  55. guard let pageHost = viewController as? PageHostingController,
  56. let index = pages.firstIndex(of: pageHost.data) else {
  57. return nil
  58. }
  59. if index + 1 == pages.count {
  60. return nil
  61. }
  62. return makeVC(page: pages[index + 1])
  63. }
  64. func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
  65. if completed,
  66. let currentViewController = pageViewController.viewControllers?.first as? PageHostingController,
  67. let currentIndex = pages.firstIndex(of: currentViewController.data)
  68. {
  69. pageDidChange?(currentIndex)
  70. }
  71. }
  72. }
  73. }


示例用法:

  1. @State var currentPage: Int = 2
  2. @State var pages = [1,2,3]
  3. var body: some View {
  4. VStack {
  5. Text(currentPage.description)
  6. .font(.largeTitle)
  7. PageView(
  8. pages: pages,
  9. currentPage: $currentPage) { num in
  10. Text("Page of \(num)")
  11. .font(.largeTitle)
  12. .padding(100)
  13. .background(Color(.systemOrange))
  14. .cornerRadius(10)
  15. }
  16. Button("Add") {
  17. pages.append(pages.count + 1)
  18. }
  19. }
  20. }

展开查看全部

相关问题