纯SwiftUI实现PageView(无UIView转接)

上文实现了使用UIScrollView转接的PageView,但其性能有些弱

本文将介绍纯SwiftUI实现的PageView


import Foundation
import SwiftUI

// new PageView in Pure SwiftUI

struct PageView: View {
    
    @Binding var currentPage: CGFloat
    let contentGetter: () -> [ViewBuilerContent]
    
    init(currentPage: Binding, @MyViewBuilder content: @escaping (() -> [ViewBuilerContent])) {
        _currentPage = currentPage
        contentGetter = content
    }
    
    private class PageCache {
        var size: CGSize = .zero
        var page: Int = 0
        var offset: CGSize = .zero
    }
    @State private var pageCache: PageCache = PageCache()
    @State private var bounceOffset: CGFloat = 0
    @State private var contentOffset: CGSize = .zero {
        didSet {
            if pageCache.size.width > 0 {
                currentPage = (contentOffset.width - bounceOffset) / pageCache.size.width
            }
        }
    }
    
    var body: some View {
        GeometryReader { geo in
            let contents = self.contentGetter()
            let size = geo.size
            
            let drag = DragGesture().onEnded { val in
                bounceOffset = 0
                // 处理左右两边、pageEnabled逻辑
                let minOffset: CGFloat = 0
                let maxOffset: CGFloat = pageCache.size.width * CGFloat(pageCache.page - 1)
                if contentOffset.width < minOffset {
                    contentOffset.width = minOffset
                } else if contentOffset.width > maxOffset {
                    contentOffset.width = maxOffset
                } else {
                    let index = Int(contentOffset.width / pageCache.size.width + 0.5)
                    contentOffset.width = pageCache.size.width * CGFloat(index)
                }
                pageCache.offset = contentOffset
            }.onChanged { val in
                // 简单模拟弹力
                let k: CGFloat = 0.6
                let minOffset: CGFloat = 0
                let maxOffset: CGFloat = pageCache.size.width * CGFloat(pageCache.page - 1)
                if contentOffset.width < minOffset {
                    let delta = abs(minOffset - contentOffset.width)
                    bounceOffset = -delta * k
                } else if contentOffset.width > maxOffset {
                    let delta = abs(maxOffset - contentOffset.width)
                    bounceOffset = delta * k
                }
                
                // 简单处理滑动
                let ch = val.translation
                contentOffset.width = pageCache.offset.width - ch.width
            }
            
            ForEach(0 ..< contents.count) { i in
                contents[i].frame(width: size.width, height: size.height).offset(x: CGFloat(i) * size.width, y: 0)
            }
            .offset(x: bounceOffset - contentOffset.width) // 注意平时scrollView的contentOffset和这个offset是相反的
            .animation(.easeInOut(duration: 0.2), value: contentOffset)
            .gesture(drag)
            .onAppear {
                pageCache.size = size
                pageCache.page = contents.count
            }
        }
    }
}

你可能感兴趣的:(纯SwiftUI实现PageView(无UIView转接))