Open fatbobman opened 1 year ago
想请问一下肘子老师,如果要从 iOS15 支持起,如何才能实现类似 scrollTargetBehavior 的特性呢? 例如我有一个类似 App Store 的页面,需要横向滚动,每个屏幕 5 个元素,每次滚动停止时让元素的左边和 scrollview 边缘对齐。
想请问一下肘子老师,如果要从 iOS15 支持起,如何才能实现类似 scrollTargetBehavior 的特性呢? 例如我有一个类似 App Store 的页面,需要横向滚动,每个屏幕 5 个元素,每次滚动停止时让元素的左边和 scrollview 边缘对齐。
想在低版本中用原生 SwiftUI 复制 scrollTargetBehavior 的特性几乎不可能。如果你的需求,仅是实现类似 App Store 的样式,可以考虑使用一些 Pager 组件,在 Github 上有不少类似的 SwiftUI 库,搜索 ( swiftui pager)。另外,也可以考虑使用 SwiftUI 中一个尚未公开的 API ( _ScrollView
不过,上述方案大多数都不支持 Lazy 特性。
如果上述方案都无法满足需求,也可以考虑寻找合适的 UIKit 库或自行包装
PS:对于简单的跨视图单页滚动,自己通过 dragGesture 实现,也不难。
@hiETsang 想请问一下肘子老师,如果要从 iOS15 支持起,如何才能实现类似 scrollTargetBehavior 的特性呢? 例如我有一个类似 App Store 的页面,需要横向滚动,每个屏幕 5 个元素,每次滚动停止时让元素的左边和 scrollview 边缘对齐。
有沒有其他更好的方法我不知道,畢竟我也只是個剛接觸程式設計的國中程式小白而已
低版本的話我是用DragGesture來實現一個頁面只有幾個View出現,但應該只能處理小量視圖
//建立一個儲存滑動量的變數
//此變數在手放開之後就會清空,所以需要在手勢結束時再做更新方法
@GestureState var dragOffset = CGSize.zero
//建立一個給予子視圖預設位置的變數
//依照自己需要的數量來決定項數即可
@State private var viewPositions: [CGFloat] = [0, 390, 390]
//0是一開始要出現的子視圖, 390式螢幕寬度外(看不到)
//再來是要前面說的會清空變數值的變數
@State private var position = CGSize.zero
//然後我的專案是會需要追蹤滑動的變量所以建立一個變數儲存
@State private var scope: Int = 0
var body: some View {
...
ZStack {
ForEach(0..<3, id: \.self) { i in
Text("\(i)")
.offset(x: viewPositions[i] + position.width + dragOffset.width)
//這裡就是:dragOffset的變量在滑動時會有,但後面結束時會被清空,所以需要在結束時把變數值丟給position,後面會用函式包裝起來
}
}
.gesture(
DragGesture(minimumDistance: 0)
//更新方法
.updating($dragOffset, body: { (currentPosition, state, transaction) in
state = currentPosition.translation
//其實這裡我也不是很懂為什麼,反正我是看著做出來ㄉ(·▷.)
})
.onEnded({ value in
processDragGesture(value)
})
)
...
}
...
func processDragGesture(_ value: GestureStateGesture<DragGesture, CGSize>.Value) {
//判斷滑動的方向
var isRight: Bool {
value.translation.width > 0
}
//判斷是否還能滑動
var isBlocked: Bool = false
self.position.width = value.translation.width
//剛才提到的,要把滑動的偏移量存到position中
if abs(value.translation.width) > 100 {//滑動值小於100就觸發其他動畫
//abs(_ x:):取絕對值
let lowStandard = 0
let highStandard = 2
if isRight {
if scope > lowStandard {
//如果大於最小值才可以觸發
isBlocked = false
//接下來就是一些簡單的位置變換
//不要問我為什麼是這樣變換 我也不知道:D 反正就試出來的
scope -= 1
viewPositions[1] = -390 + position.width
viewPositions[2] = 0 + position.width
withAnimation(.easeOut(duration: 0.3)) {
viewPositions[0] = 0
viewPositions[1] = 0
viewPositions[2] = 390
}
viewPositions[0] = -390
}else {
isBlocked = true
}
}else {
if scope < highStandard {
isBlocked = false
scope += 1
viewPositions[0] = 0 + position.width
viewPositions[1] = 390 + position.width
viewPositions[2]+ position.width
withAnimation(.easeOut(duration: 0.3)) {
viewPositions[0] = -390
viewPositions[1] = 0
}
viewPositions[2] = 390
}else {
isBlocked = true
}
}
}
if abs(value.translation.width) < 100 || isBlocked {
withAnimation(.interpolatingSpring(stiffness: 50, damping: 7, initialVelocity: 5)) {
self.position.width = 0
}
}else {
self.position.width = 0
}
}
...
說實在的這個算法很爛,但我也想不到什麼方法可以改目前的算法,畢竟這也是我第一個專案,雖然還在維護,但我還要重寫就覺得很麻煩,app叫Gremo,不介意的話可以幫我上AppStore上支持一下:D
第二处 scrollTargetLayout(
的位置写错了,应该修饰 LazyVStack 而不是 ScrollView
@Saafo 今晚会改过来,谢谢
https://www.fatbobman.com/posts/new-features-of-ScrollView-in-SwiftUI5/
在 SwiftUI 5.0 中,苹果大幅强化了 ScrollView 功能。新增了大量新颖、完善的 API。本文将对这些新功能进行介绍,希望能够让它们更多、更早的帮助到有需要的开发者。