UIPageViewController 在 SwiftUI 中滑动 1 次后停止工作 [英] UIPageViewController stops working after 1 swipe in SwiftUI

查看:36
本文介绍了UIPageViewController 在 SwiftUI 中滑动 1 次后停止工作的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在关注 SwiftUI 的 Apple 教程,并在与 UIKit 交互"下(https://developer.apple.com/tutorials/swiftui/interface-with-uikit)我们被用来制作一个 UIPageController.

在本教程中,他们使用 State 属性来更新 PageView 中的 currentPage 变量.我想尝试一下,并决定将 PageView 嵌入到不同的视图中,并在该父视图中更新 currentPage 属性.因此,很自然地,我将 @State 属性更改为 PageView 中的 @Binding 属性,以便更新 currentPage在父级中.

但是,当我这样做时,UIPageController 停止工作并不断从 pageViewController(_:viewControllerBefore:) -> 返回 nil ->UIViewController? 第二次滑动.

这是代码,我将添加注释以使其不那么乏味:

struct ContentView: 查看 {@State var currentPage = 0//这是应该更新的新@State 变量.var主体:一些视图{虚拟堆栈{PageView([Text("First page"), Text("Second Page"), Text("Third Page")],currentPage: self.$currentPage)//将绑定传递给 State 变量,因为我希望在页面更改时更新它.Text("Page number: \(currentPage)")//删除这个可以解决问题,但我在这里需要它.}}}

struct PageView: View {var viewControllers: [UIHostingController]@Binding var currentPage: Int//这曾经是一个@State 属性,但我将其更改为绑定,因为 currentPage 变量现在正在其他地方更新(在上面的视图中).init(_views: [Page], currentPage: Binding) {self.viewControllers = views.map { UIHostingController(rootView: $0) }//简单地将所有视图包装在 ViewControllers 中供 UIPageViewController 使用.self._currentPage = currentPage//分配绑定.}var主体:一些视图{PageViewController(控制器:self.viewControllers,currentPage:self.$currentPage)}}

struct PageViewController: UIViewControllerRepresentable {var 控制器:[UIViewController]@Binding var currentPage: Intfunc makeCoordinator() ->Coordinator {//使协调器,与问题无关协调员(自己)}func makeUIViewController(context: Context) ->UIPageViewController {//我不认为问题出在这里.让 pageViewController = UIPageViewController(过渡风格:.scroll,导航方向:.horizo​​ntal)pageViewController.dataSource = context.coordinatorpageViewController.delegate = context.coordinator返回页面ViewController}func updateUIViewController(_ pageViewController: UIPageViewController, context: Context) {//我不认为问题出在这里.pageViewController.setViewControllers([self.controllers[currentPage]],方向:.forward,动画:true)}类协调器:NSObject、UIPageViewControllerDataSource、UIPageViewControllerDelegate {var 父级:PageViewControllerinit(_ pageViewController: PageViewController) {self.parent = pageViewController}func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) ->UIViewController?{//这就是我认为问题所在.当评估下面的行时,第二次滑动时会返回 nil ,而不应该是.它适用于第一次滑动,但在第二次滑动时返回 nil,因此没有显示任何内容.守卫让索引 = parent.controllers.firstIndex(of: viewController) else {返回零}如果索引 == 0 {返回 parent.controllers.last}返回 parent.controllers[index - 1]}func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) ->UIViewController?{守卫让索引 = parent.controllers.firstIndex(of: viewController) else {返回零}如果索引 == parent.controllers.count - 1 {返回 parent.controllers.first}返回 parent.controllers[index + 1]}func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating 完成: Bool, previousViewControllers: [UIViewController], transitionCompleted Completed: Bool) {如果完成,让visibleViewController = pageViewController.viewControllers?.first,让index = parent.controllers.firstIndex(of:visibleViewController) {parent.currentPage = 索引}}}}

此外,ContentView 中删除 Text 修复了问题,但我需要它 - 所以删除它不是一个选项.

如果有人能解释如何解决我的问题,我将不胜感激.如果您太懒,则无需提供代码示例,简单的解释也应该可以完成工作.

提前致谢!

解决方案

Text 刷新时,body 更新时会重新创建 PageView.避免这种情况的解决方案是让 PageView 确认为 Equatable.

最简单的修复demo如下(仅修改部分).使用 Xcode 11.4/iOS 13.4 测试

注意:使用相同的PageView,因为没有提供PageView2

//...var主体:一些视图{虚拟堆栈{PageView([Text("First page"), Text("Second Page"), Text("Third Page")],currentPage: self.$currentPage).equatable()//<<这里 !!Text("页码:\(currentPage)")}}struct PageView<Page: View>: View, Equatable {//<<确认 !!static func == (lhs: PageView, rhs: PageView) ->布尔{return true//仅用于演示,使其在某些属性上有意义}//.. 其他代码放在这里

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!

解决方案

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

这篇关于UIPageViewController 在 SwiftUI 中滑动 1 次后停止工作的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆