SwiftUI:传递ScrollView偏移值至Page View Controller实现顶部视图切换
滚动到底部切换顶部视图为PageControl的实现方案
问题描述
需要在CenterHomeView的ScrollView滚动到底部时,隐藏顶部的DayTopView,替换为PageControl;目前已能获取ScrollView的偏移值,但无法将其传递到上层的ScrolViewSetOffsetView中控制视图切换。
解决方案
1. 补充缺失的ViewOffsetKey定义
补上代码中缺失的偏好键,用于传递滚动偏移值:
struct ViewOffsetKey: PreferenceKey { static var defaultValue: CGFloat = 0 static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { value = nextValue() } }
2. 修改CenterHomeView,添加偏移值绑定
给CenterHomeView添加@Binding属性,将滚动偏移值传递给父视图:
struct CenterHomeView: View { @Binding var day: Int // 添加滚动偏移绑定 @Binding var scrollOffset: CGFloat var body: some View { ZStack { if day == 0 { ScrollView { VStack { ForEach(0..<100){ index in Text("\(index)") }.frame(width: 100) } .background(GeometryReader { proxy in Color.clear.preference( key: ViewOffsetKey.self, value: -proxy.frame(in: .named("scroll")).origin.y ) }) .onPreferenceChange(ViewOffsetKey.self) { offset in scrollOffset = offset } } .coordinateSpace(name: "scroll") } else if day == 1 { Color.blue } else if day == 2 { Color.gray } else if day == 3 { Color.green } else if day == 4 { Color.red } else if day == 5 { Color.yellow } else if day == 6 { Color.pink } else if day == 7 { Color.blue } Spacer() } .hiddenNavigationBarStyle() } }
3. 调整ScrolViewSetOffsetView,接收并使用偏移值
修改ScrolViewSetOffsetView,添加滚动偏移状态变量,调整顶部视图显示逻辑:
struct ScrolViewSetOffsetView: View { @Binding var day: Int // 保存滚动偏移值 @State private var scrollOffset: CGFloat = 0 @Binding var views: [HostingController<CenterHomeView>] // 判断是否滚动到底部(可根据实际需求调整阈值) private var isScrolledToBottom: Bool { scrollOffset >= 80 } var body: some View { NavigationView { VStack { if !isScrolledToBottom { DayTopView(currentDay: $day) } else { PageControl(numberOfPages: 7, currentPage: $day) .frame(width: CGFloat(7 * 18)) .padding(.trailing) } PageViewController(pages: views, currentPage: $day, scrollOffset: $scrollOffset) } } } }
4. 修改PageViewController和HostingController,传递偏移绑定
更新PageViewController接收滚动偏移绑定,并传递给对应子视图:
struct PageViewController: UIViewControllerRepresentable { var pages: [UIViewController] @Binding var currentPage: Int // 添加滚动偏移绑定 @Binding var scrollOffset: CGFloat 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 return pageViewController } func updateUIViewController(_ pageViewController: UIPageViewController, context: Context) { context.coordinator.parent = self pageViewController.setViewControllers([pages[currentPage]], direction: .forward, animated: false) // 更新当前显示视图的偏移绑定 if let hostingVC = pages[currentPage] as? HostingController<CenterHomeView> { hostingVC.rootView.scrollOffset = scrollOffset } } class Coordinator: NSObject, UIPageViewControllerDataSource, UIPageViewControllerDelegate,UIScrollViewDelegate { var parent: PageViewController init(_ pageViewController: PageViewController) { self.parent = pageViewController } func pageViewController( _ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { guard let index = parent.pages.firstIndex(of: viewController) else { return nil } if index == 0 { return nil } return parent.pages[index - 1] } func pageViewController( _ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? { guard let index = parent.pages.firstIndex(of: viewController) else { return nil } if index + 1 == parent.pages.count { return nil } return parent.pages[index + 1] } func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) { if completed, let visibleViewController = pageViewController.viewControllers?.first, let index = parent.pages.firstIndex(of: visibleViewController) { parent.currentPage = index // 切换页面后重置偏移值 parent.scrollOffset = 0 } } } } // 更新MainHomePage中HostingController的创建逻辑 struct MainHomePage: View { @State private var day = 0 @State var days: [HostingController<CenterHomeView>] = [] init() { var pages = [HostingController<CenterHomeView>] () for index in 0..<7 { let view = CenterHomeView(day: .constant(index), scrollOffset: .constant(0)) pages.append(HostingController(rootView: view)) } self.days = pages } var body: some View { TabView { ScrolViewSetOffsetView(day: $day, views: $days) .tabItem { Image(systemName: "person") Text("Profile") } } } }
关键逻辑说明
- 通过
PreferenceKey捕获ScrollView滚动偏移,再用@Binding将值从子视图向上传递到父视图 - 在
ScrolViewSetOffsetView中根据偏移阈值判断是否切换顶部视图,可根据实际需求调整阈值,或通过计算内容高度与容器高度的差值实现精确的"滚动到底部"判断 - 切换Page页面时重置偏移值,避免视图状态异常
内容的提问来源于stack exchange,提问作者Sham Dhiman
相关产品推荐
相关产品推荐

