ios - UIPageViewController 滑动忽略 SwiftUI 的 navigationBarHidden(true)

标签 ios swift swiftui

我正在创建一个需要包含数百个页面的 slider 的 SwiftUI 应用程序。由于没有适合我需求的第一方解决方案,我已经适配了UIPageViewController。主要用例: slider 应允许点击其内容,这将(取消)隐藏导航栏。隐藏单独使用时效果很好,但当我在导航栏隐藏时滑动时,它会重新出现。

如何根据navigationBarHidden(hidden)隐藏导航栏?

一步一步:

  1. 点击隐藏(导航栏现在隐藏)
  2. 滑动(仍然隐藏)
  3. 滑动(出现)

Demo video

在大多数情况下,需要滑动两次才能取消隐藏栏,但有时只需滑动一次即可取消隐藏。

这是应用程序的 SwiftUI 部分:

struct ApplicationView: View {

    var body: some View {
        NavigationView {
            ContentView()
                .navigationBarTitle(Text("Test"), displayMode: .inline)
        }
    }

}

struct ContentView: View {

    @State private var hidden = false
    @State private var currentPage = 0

    var body: some View {
        PagerView(0..<100, currentPage: $currentPage) {
            Text("\($0)")
                .frame(maxWidth: .infinity, maxHeight: .infinity)
                .onTapGesture { hidden.toggle() }
        }
        .navigationBarHidden(hidden)
        .ignoresSafeArea()
        .background(Color.red.ignoresSafeArea())
    }

}

和改编的UIPageViewController:

struct PagerView<Data: RandomAccessCollection, Page: View>: UIViewControllerRepresentable {

    private let data: Data
    @Binding var currentPage: Data.Index
    private let interPageSpacing: CGFloat
    private let content: (Data.Element) -> Page

    init(
        _ data: Data,
        currentPage: Binding<Data.Index>,
        interPageSpacing: CGFloat = 0,
        @ViewBuilder content: @escaping (Data.Element) -> Page
    ) {
        self.data = data
        self._currentPage = currentPage
        self.interPageSpacing = interPageSpacing
        self.content = content
    }

    func makeUIViewController(context: Context) -> UIPageViewController {
        let pageViewController = UIPageViewController(
            transitionStyle: .scroll,
            navigationOrientation: .horizontal,
            options: [.interPageSpacing: interPageSpacing]
        )
        pageViewController.delegate = context.coordinator
        pageViewController.dataSource = context.coordinator
        return pageViewController
    }

    func updateUIViewController(_ uiViewController: UIPageViewController, context: Context) {
        let direction: UIPageViewController.NavigationDirection

        if let previousViewController = uiViewController.viewControllers?.first as? PageViewController<Page> {
            guard previousViewController.index != currentPage else { return }
            direction = previousViewController.index < currentPage ? .forward : .reverse
        } else {
            direction = .forward
        }

        let page = context.coordinator.page(for: currentPage)
        uiViewController.setViewControllers([page], direction: direction, animated: true)
    }

    func makeCoordinator() -> Coordinator {
        return Coordinator(self)
    }

    class Coordinator: NSObject, UIPageViewControllerDelegate, UIPageViewControllerDataSource {

        private var parent: PagerView

        init(_ parent: PagerView) {
            self.parent = parent
        }

        func page(for index: Data.Index) -> PageViewController<Page> {
            return PageViewController(rootView: parent.content(parent.data[index]), index: index)
        }

        func pageViewController(
            _ pageViewController: UIPageViewController,
            viewControllerBefore viewController: UIViewController
        ) -> UIViewController? {
            guard parent.currentPage > parent.data.startIndex else { return nil }
            return page(for: parent.data.index(parent.currentPage, offsetBy: -1))
        }

        func pageViewController(
            _ pageViewController: UIPageViewController,
            viewControllerAfter viewController: UIViewController
        ) -> UIViewController? {
            guard parent.currentPage < parent.data.index(parent.data.endIndex, offsetBy: -1) else { return nil }
            return page(for: parent.data.index(parent.currentPage, offsetBy: 1))
        }

        func pageViewController(
            _ pageViewController: UIPageViewController,
            didFinishAnimating finished: Bool,
            previousViewControllers: [UIViewController],
            transitionCompleted completed: Bool
        ) {
            if completed, let viewController = pageViewController.viewControllers?.first as? PageViewController<Page> {
                parent.currentPage = viewController.index
            }
        }

    }

    class PageViewController<Content: View>: UIHostingController<Content> {

        var index: Data.Index

        init(rootView: Content, index: Data.Index) {
            self.index = index
            super.init(rootView: rootView)
        }

        @MainActor @objc required dynamic init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }

        override func viewDidLoad() {
            super.viewDidLoad()
            view.backgroundColor = .clear
        }

    }

}

最佳答案

我看到您嵌入了一个 UIHostingController。我发现将其作为 subview 会导致导航栏重新出现,因为在某些情况下它会将 hidden 设置为 false(不确定何时)。

尝试 this answer 中的方法,从您的 UIHostingController 中删除对 UINavigationController 的访问:

class PageViewController<Content: View>: UIHostingController<Content> {

    public override var navigationController: UINavigationController? {
        nil
    }

    ...
}

(这确实是 Apple 应该解决的问题。目前 UIHostingController 在嵌入到 SwiftUI View 层次结构中时并不能很好地发挥作用。)

关于ios - UIPageViewController 滑动忽略 SwiftUI 的 navigationBarHidden(true),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69883830/

相关文章:

ios - iOS 应用程序的典型目录结构

ios - 保存字符串以快速解析数组

ios - 只有根级导航目的地对具有同构路径的导航堆栈有效

accessibility - Catalyst 'SwiftUI.AccessibilityNode' 不是已知的可序列化元素

ios - 如何快速添加从我的 appdelegate 类调用的弹出窗口?

iOS MVC : get view controller class from model?

ios - 将新行添加到具有动态高度单元格的 TableView

ios - 如何在自定义 TitleView 的 subview 上添加 subview

swift - willSet 问题中的延迟初始化

ios - 如何使用 SwiftUI 复制外观粗糙的笔记应用程序背景?