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
相关产品推荐
相关产品推荐

