低功耗模式下在 SwiftUI 中使用 UIPageViewController 时导航栏元素消失

问题描述 投票:0回答:1

问题描述:

在 SwiftUI 应用程序中,我使用 UIViewControllerRepresentable 封装了 UIKit 的 UIPageViewController,并将封装的类命名为 PagedInfiniteScrollView。该组件会导致导航栏元素(标题和按钮)消失。 此问题仅在物理设备上的低功耗模式下发生。

重现步骤:

  1. 在物理设备上启用低功耗模式并打开应用程序的主页。

  2. 从主页中,打开包含 PagedInfiniteScrollView 的详细信息表。此详细信息页面应在右上角包含导航标题和工具栏按钮。 PagedInfiniteScrollView 支持水平滑动切换页面。

  3. 点击详细信息页面右上角的工具栏按钮可打开编辑表。 不进行任何更改,关闭编辑表并返回到详细信息页面。 在详情页面,在PagedInfiniteScrollView上左右滑动。

import SwiftUI

@main
struct CalendarApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}


import SwiftUI

struct ContentView: View {
    @State private var showDetailSheet = false
    @State private var currentPage: Int = 0

    var body: some View {
        NavigationStack {
            Button {
                showDetailSheet = true
            } label: {
                Text("show Calendar sheet")
            }
            .sheet(isPresented: $showDetailSheet) {
                DetailSheet(currentPage: $currentPage)
            }
        }
    }
}

struct DetailSheet: View {
    @Binding var currentPage: Int
    @State private var showEditSheet = false

    var body: some View {
        NavigationStack {
            PagedInfiniteScrollView(content: { pageIndex in
                                        Text("\(pageIndex)")
                                            .frame(width: 200, height: 200)
                                            .background(Color.blue)
                                    },
                                    currentPage: $currentPage)
                .sheet(isPresented: $showEditSheet, content: {
                    Text("edit")
                })

                .navigationTitle("Detail")
                .navigationBarTitleDisplayMode(.inline)
                .toolbar {
                    ToolbarItemGroup(placement: .topBarTrailing) {
                        Button {
                            showEditSheet = true
                        } label: {
                            Text("Edit")
                        }
                    }
                }
        }
    }
}



import SwiftUI
import UIKit

struct PagedInfiniteScrollView<Content: View>: UIViewControllerRepresentable {
    typealias UIViewControllerType = UIPageViewController

    let content: (Int) -> Content
    @Binding var currentPage: Int

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

    func makeUIViewController(context: Context) -> UIPageViewController {
        let pageViewController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal)
        pageViewController.dataSource = context.coordinator
        pageViewController.delegate = context.coordinator

        let initialViewController = UIHostingController(rootView: IdentifiableContent(index: currentPage, content: { content(currentPage) }))
        pageViewController.setViewControllers([initialViewController], direction: .forward, animated: false, completion: nil)

        return pageViewController
    }

    func updateUIViewController(_ uiViewController: UIPageViewController, context: Context) {
        let currentViewController = uiViewController.viewControllers?.first as? UIHostingController<IdentifiableContent<Content>>
        let currentIndex = currentViewController?.rootView.index ?? 0

        if currentPage != currentIndex {
            let direction: UIPageViewController.NavigationDirection = currentPage > currentIndex ? .forward : .reverse
            let newViewController = UIHostingController(rootView: IdentifiableContent(index: currentPage, content: { content(currentPage) }))
            uiViewController.setViewControllers([newViewController], direction: direction, animated: true, completion: nil)
        }
    }

    class Coordinator: NSObject, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
        var parent: PagedInfiniteScrollView

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

        func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
            guard let currentView = viewController as? UIHostingController<IdentifiableContent<Content>>, let currentIndex = currentView.rootView.index as Int? else {
                return nil
            }

            let previousIndex = currentIndex - 1

            return UIHostingController(rootView: IdentifiableContent(index: previousIndex, content: { parent.content(previousIndex) }))
        }

        func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
            guard let currentView = viewController as? UIHostingController<IdentifiableContent<Content>>, let currentIndex = currentView.rootView.index as Int? else {
                return nil
            }

            let nextIndex = currentIndex + 1

            return UIHostingController(rootView: IdentifiableContent(index: nextIndex, content: { parent.content(nextIndex) }))
        }

        func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
            if completed,
               let currentView = pageViewController.viewControllers?.first as? UIHostingController<IdentifiableContent<Content>>,
               let currentIndex = currentView.rootView.index as Int? {
                parent.currentPage = currentIndex
            }
        }
    }
}

extension PagedInfiniteScrollView {
    struct IdentifiableContent<Content: View>: View {
        let index: Int
        let content: Content

        init(index: Int, @ViewBuilder content: () -> Content) {
            self.index = index
            self.content = content()
        }

        var body: some View {
            content
        }
    }
}


预期结果:

滑动 PagedInfiniteScrollView 时,详细信息页面的导航标题和右上角的工具栏按钮应保持可见。

实际结果:

滑动PagedInfiniteScrollView时,详情页的导航标题和右上角工具栏按钮消失。

swiftui uikit
1个回答
0
投票

🎊恭喜💐😋 立即向我们获取您的优惠券。点击链接,提交您的 Gmail 和邮政编码以获取您的优惠券。 赶快在这里注册吧🤫👇👇👇

在 SwiftUI 环境中使用

UIPageViewController
时,您有时可能会遇到导航栏元素消失的意外行为,尤其是在低功耗模式等条件下。这是因为低功耗模式会影响动画和视图更新,导致不一致。

🎊恭喜💐😋 立即向我们获取您的优惠券。点击链接,提交您的 Gmail 和邮政编码以获取您的优惠券。 赶快在这里注册吧🤫👇👇👇 https://cutt.ly/5eWF0Ffn

问题分析

UIPageViewController
嵌入到 SwiftUI
UIViewControllerRepresentable
包装器中时,导航栏可能会在视图更新期间丢失其状态。在低功耗模式下,某些系统优化可能会干扰视图的渲染和生命周期。这可能会导致导航项目消失或无法正确设置。

解决方案概述

要解决此问题,您需要确保每次

UIPageViewController
更新或显示新视图时都显式设置导航栏元素。这可以通过以下方式完成:

  1. 显式设置导航栏项目: 以不受

    UIPageViewController
    内状态更改影响的方式添加导航栏项目。一种常见的方法是在托管
    .navigationBarItems
    的 SwiftUI 视图上使用 SwiftUI 的
    UIPageViewController
    修饰符。

  2. 正确处理状态: 如果导航栏项目在

    UIPageViewController
    内进行管理,请确保页面转换等状态更改不会干扰导航栏配置。

实现:SwiftUI + UIPageViewController 集成

以下是解决此问题的一般结构:

  1. 创建

    UIViewControllerRepresentable
    包装:

    import SwiftUI
    import UIKit
    
    struct PageViewController: UIViewControllerRepresentable {
        var viewControllers: [UIViewController]
    
        func makeUIViewController(context: Context) -> UIPageViewController {
            let pageViewController = UIPageViewController(
                transitionStyle: .scroll,
                navigationOrientation: .horizontal
            )
            return pageViewController
        }
    
        func updateUIViewController(_ uiViewController: UIPageViewController, context: Context) {
            uiViewController.setViewControllers(
                [viewControllers[0]], direction: .forward, animated: true
            )
        }
    }
    
  2. 将其包装在 SwiftUI 视图中并配置导航:

    在父 SwiftUI 视图中,即使在省电状态下,您也可以确保正确设置和显示导航项:

    struct PageView: View {
        var body: some View {
            NavigationView {
                PageViewController(viewControllers: [
                    UIHostingController(rootView: Text("Page 1")),
                    UIHostingController(rootView: Text("Page 2"))
                ])
                .navigationBarTitle("Page View", displayMode: .inline)
                .navigationBarItems(trailing: Button("Action") {
                    print("Action tapped")
                })
            }
        }
    }
    

解决低功耗模式问题的技巧

  1. 避免大量动画: SwiftUI 和 UIKit 动画在低功耗模式下可能会受到影响。使用更简单的动画或有条件地禁用它们。

  2. 检查查看生命周期事件: 监控视图的生命周期(

    onAppear
    onDisappear
    )以确保导航项设置正确。

  3. 调试状态更改: 使用

    print
    语句或 Xcode 的调试工具来查看导航栏项目何时以及在什么情况下消失。

通过显式设置导航栏项目并确保它们不依赖于

UIPageViewController
内的状态更改,您应该能够克服低功耗模式的不一致问题。

🎊恭喜💐😋 立即向我们获取您的优惠券。点击链接,提交您的 Gmail 和邮政编码以获取您的优惠券。 赶快在这里注册吧🤫👇👇👇 https://cutt.ly/5eWF0Ffn

© www.soinside.com 2019 - 2024. All rights reserved.