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

iOS 16.4中SwiftUI的ScrollView pagingEnabled失效问题及解决办法

SwiftUI垂直分页实现方案(iOS 16.4+)

原方案失效说明

iOS 16.4后,苹果调整了SwiftUI ScrollView的底层实现逻辑,通过UIScrollView.appearance().isPagingEnabled = true全局开启分页的方式不再生效,且无官方修复途径,不建议继续尝试此方案。

替代实现方式

方式一:封装UIKit的UIScrollView

通过UIViewRepresentable直接封装UIKit的UIScrollView,手动启用分页,兼容性稳定:

struct PagingScrollView<Content: View>: UIViewRepresentable {
    let isVertical: Bool
    let content: Content
    
    init(isVertical: Bool, @ViewBuilder content: () -> Content) {
        self.isVertical = isVertical
        self.content = content()
    }
    
    func makeUIView(context: Context) -> UIScrollView {
        let scrollView = UIScrollView()
        scrollView.isPagingEnabled = true
        scrollView.showsVerticalScrollIndicator = false
        scrollView.showsHorizontalScrollIndicator = false
        
        let hostingView = UIHostingController(rootView: content).view
        hostingView?.translatesAutoresizingMaskIntoConstraints = false
        guard let hostingView = hostingView else { return scrollView }
        
        scrollView.addSubview(hostingView)
        
        NSLayoutConstraint.activate([
            hostingView.topAnchor.constraint(equalTo: scrollView.topAnchor),
            hostingView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
            hostingView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
            hostingView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
            hostingView.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
            // 垂直分页时,内容高度为单个页面高度 × 页面数量,需根据实际内容调整
            hostingView.heightAnchor.constraint(equalTo: scrollView.heightAnchor, multiplier: isVertical ? 3 : 1)
        ])
        
        return scrollView
    }
    
    func updateUIView(_ uiView: UIScrollView, context: Context) {}
}

// 使用示例
struct ContentView: View {
    var body: some View {
        PagingScrollView(isVertical: true) {
            VStack(spacing: 0) {
                Color.red.frame(maxWidth: .infinity, maxHeight: .infinity)
                Color.blue.frame(maxWidth: .infinity, maxHeight: .infinity)
                Color.green.frame(maxWidth: .infinity, maxHeight: .infinity)
            }
        }
        .ignoresSafeArea()
    }
}

方式二:纯SwiftUI实现分页(结合GeometryReader)

通过监听滚动偏移量,配合ScrollViewReader(iOS 16+)或scrollPosition(iOS 17+)实现分页逻辑:

struct VerticalPagingScrollView<Content: View>: View {
    private let pages: [Content]
    @State private var currentPage = 0
    @State private var scrollOffset: CGFloat = 0
    
    init(@ViewBuilder pages: () -> [Content]) {
        self.pages = pages()
    }
    
    var body: some View {
        GeometryReader { geo in
            ScrollViewReader { proxy in
                ScrollView(.vertical, showsIndicators: false) {
                    VStack(spacing: 0) {
                        ForEach(0..<pages.count, id: \.self) { index in
                            pages[index]
                                .frame(width: geo.size.width, height: geo.size.height)
                                .id(index)
                        }
                    }
                    .background(
                        GeometryReader { innerGeo in
                            Color.clear
                                .preference(key: ScrollOffsetKey.self, value: innerGeo.frame(in: .named("scroll")).minY)
                        }
                    )
                }
                .coordinateSpace(name: "scroll")
                .onPreferenceChange(ScrollOffsetKey.self) { offset in
                    scrollOffset = -offset
                    let calculatedPage = Int(round(scrollOffset / geo.size.height))
                    guard calculatedPage != currentPage, calculatedPage >= 0, calculatedPage < pages.count else { return }
                    
                    currentPage = calculatedPage
                    proxy.scrollTo(calculatedPage, anchor: .top)
                }
            }
        }
        .ignoresSafeArea()
    }
}

struct ScrollOffsetKey: PreferenceKey {
    static var defaultValue: CGFloat = 0
    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
        value = nextValue()
    }
}

// 使用示例
struct ContentView: View {
    var body: some View {
        VerticalPagingScrollView {
            [
                Color.red,
                Color.blue,
                Color.green
            ]
        }
    }
}

如果是iOS 17及以上,可以简化为使用scrollPosition API替代ScrollViewReader:

ScrollView(.vertical, showsIndicators: false) {
    // ... 内容部分不变
}
.scrollPosition(id: $currentPage)

内容的提问来源于stack exchange,提问作者John

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 03:13:24