SwiftUI 高级之水平滚动并获取滚动位置
2020-02-03 本文已影响0人
iCloudEnd
效果
Jietu20200204-213912@2x.jpg代码
Page代码
//
// PagingScrollView.swift
// SwiftUIPagingScrollView
//
// Created by myf on 27/08/2019.
// Copyright © 2019 Pavel Zak. All rights reserved.
//
import SwiftUI
struct PagingScrollView: View {
let items: [AnyView]
init<A: View>(activePageIndex:Binding<Int>, itemCount: Int, pageWidth:CGFloat, tileWidth:CGFloat, tilePadding: CGFloat, @ViewBuilder content: () -> A) {
let views = content()
self.items = [AnyView(views)]
self._activePageIndex = activePageIndex
self.pageWidth = pageWidth
self.tileWidth = tileWidth
self.tilePadding = tilePadding
self.tileRemain = (pageWidth-tileWidth-2*tilePadding)/2
self.itemCount = itemCount
self.contentWidth = (tileWidth+tilePadding)*CGFloat(self.itemCount)
self.leadingOffset = tileRemain+tilePadding
self.stackOffset = contentWidth/2 - pageWidth/2 - tilePadding/2
}
/// index of current page 0..N-1
@Binding var activePageIndex : Int
/// pageWidth==frameWidth used to properly compute offsets
let pageWidth: CGFloat
/// width of item / tile
let tileWidth : CGFloat
/// padding between items
private let tilePadding : CGFloat
/// how much of surrounding iems is still visible
private let tileRemain : CGFloat
/// total width of conatiner
private let contentWidth : CGFloat
/// offset to scroll on the first item
private let leadingOffset : CGFloat
/// since the hstack is centered by default this offset actualy moves it entirely to the left
private let stackOffset : CGFloat // to fix center alignment
/// number of items; I did not come with the soluion of extracting the right count in initializer
private let itemCount : Int
/// some damping factor to reduce liveness
private let scrollDampingFactor: CGFloat = 0.66
/// current offset of all items
@State var currentScrollOffset: CGFloat = 0
/// drag offset during drag gesture
@State private var dragOffset : CGFloat = 0
func offsetForPageIndex(_ index: Int)->CGFloat {
let activePageOffset = CGFloat(index)*(tileWidth+tilePadding)
return self.leadingOffset - activePageOffset
}
func indexPageForOffset(_ offset : CGFloat) -> Int {
guard self.itemCount>0 else {
return 0
}
let offset = self.logicalScrollOffset(trueOffset: offset)
let floatIndex = (offset)/(tileWidth+tilePadding)
var computedIndex = Int(round(floatIndex))
computedIndex = max(computedIndex, 0)
return min(computedIndex, self.itemCount-1)
}
/// current scroll offset applied on items
func computeCurrentScrollOffset()->CGFloat {
return self.offsetForPageIndex(self.activePageIndex) + self.dragOffset
}
/// logical offset startin at 0 for the first item - this makes computing the page index easier
func logicalScrollOffset(trueOffset: CGFloat)->CGFloat {
return (trueOffset-leadingOffset) * -1.0
}
var body: some View {
GeometryReader { outerGeometry in
HStack(alignment: .center, spacing: self.tilePadding) {
/// building items into HStack
ForEach(0..<self.items.count) { index in
self.items[index]
.offset(x: self.currentScrollOffset, y: 0)
.frame(width: self.tileWidth)
}
}
.onAppear {
self.currentScrollOffset = self.offsetForPageIndex(self.activePageIndex)
}
.offset(x: self.stackOffset, y: 0)
.background(Color.black.opacity(0.00001)) // hack - this allows gesture recognizing even when background is transparent
.frame(width: self.contentWidth)
.simultaneousGesture( DragGesture(minimumDistance: 1, coordinateSpace: .local) // can be changed to simultaneous gesture to work with buttons
.onChanged { value in
self.dragOffset = value.translation.width
self.currentScrollOffset = self.computeCurrentScrollOffset()
}
.onEnded { value in
// compute nearest index
let velocityDiff = (value.predictedEndTranslation.width - self.dragOffset)*self.scrollDampingFactor
let newPageIndex = self.indexPageForOffset(self.currentScrollOffset+velocityDiff)
self.dragOffset = 0
withAnimation(.interpolatingSpring(mass: 0.1, stiffness: 20, damping: 1.5, initialVelocity: 0)){
self.activePageIndex = newPageIndex
self.currentScrollOffset = self.computeCurrentScrollOffset()
}
}
)
}
}
}
界面
import SwiftUI
struct TileView: View {
let icon: String
var color: Color
var body: some View {
VStack {
ZStack {
Rectangle()
.fill(color)
.cornerRadius(20.0)
Image(systemName: icon)
.imageScale(.large)
.font(.largeTitle)
}
}
}
}
struct ContentView: View {
@State private var scrollEffectValue: Double = 13
@State private var activePageIndex: Int = 0
let tileWidth: CGFloat = 220
let tilePadding: CGFloat = 20
let numberOfTiles: Int = 10
var items = [Color.red, Color.orange, Color.yellow, Color.green, Color.blue, Color.purple,Color.red, Color.orange, Color.yellow, Color.green, Color.blue, Color.purple]
var body: some View {
VStack {
Spacer()
GeometryReader { geometry in
PagingScrollView(activePageIndex: self.$activePageIndex, itemCount:self.numberOfTiles ,pageWidth:geometry.size.width, tileWidth:self.tileWidth, tilePadding: self.tilePadding){
ForEach(0 ..< self.numberOfTiles) { index in
GeometryReader { geometry2 in
TileView(icon: "\(index + 1).circle",color:self.items[index])
.rotation3DEffect(Angle(degrees: Double((geometry2.frame(in: .global).minX - self.tileWidth*0.5) / -10 )), axis: (x: 2, y: 11, z: 1))
.onTapGesture {
print ("tap on index: \(index) current:\(self.$activePageIndex)")
}
}
}
}
}.frame(height:300)
List{
Text("current:\(self.activePageIndex)")
Text("current:\(self.items[self.activePageIndex].description)")
}
Spacer()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
源码参考
https://github.com/izakpavel/SwiftUIPagingScrollView
更多SwiftUI教程和代码关注专栏
QQ:3365059189
SwiftUI技术交流QQ群:518696470
- 请关注我的专栏icloudend, SwiftUI教程与源码
https://www.jianshu.com/c/7b3e3b671970