上文實(shí)現(xiàn)了使用UIScrollView轉(zhuǎn)接的PageView,但其性能有些弱
本文將介紹純SwiftUI實(shí)現(xiàn)的PageView
import Foundation
import SwiftUI
// new PageView in Pure SwiftUI
struct PageView<ViewBuilerContent: View>: View {
@Binding var currentPage: CGFloat
let contentGetter: () -> [ViewBuilerContent]
init(currentPage: Binding<CGFloat>, @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
// 簡(jiǎn)單模擬彈力
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
}
// 簡(jiǎn)單處理滑動(dòng)
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) // 注意平時(shí)scrollView的contentOffset和這個(gè)offset是相反的
.animation(.easeInOut(duration: 0.2), value: contentOffset)
.gesture(drag)
.onAppear {
pageCache.size = size
pageCache.page = contents.count
}
}
}
}