You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 07:10:31