Hel*_*imo 11 scrollview ios13 swiftui
问题
如何修改scrollView的滚动目标?我正在寻找一种替代“经典” scrollView委托方法的方法
override func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>)
......在这里我们可以modfify目标scrollView.contentOffset通过targetContentOffset.pointee例如创建一个自定义分页行为。
或者换句话说:我确实想在(水平)scrollView中创建分页效果。
我尝试过的就是 是这样的:
ScrollView(.horizontal, showsIndicators: true, content: {
HStack(alignment: VerticalAlignment.top, spacing: 0, content: {
card(title: "1")
card(title: "2")
card(title: "3")
card(title: "4")
})
})
// 3.
.content.offset(x: self.dragState.isDragging == true ? self.originalOffset : self.modifiedOffset, y: 0)
// 4.
.animation(self.dragState.isDragging == true ? nil : Animation.spring())
// 5.
.gesture(horizontalDragGest)
Run Code Online (Sandbox Code Playgroud)
尝试
这是我尝试过的方法(除了自定义的scrollView方法):
scrollView的内容区域大于屏幕空间,因此根本无法滚动。
我创建了一个,DragGesture()以检测是否有阻力在继续。在.onChanged和.onEnded闭包中,我修改了@State值以创建所需的scrollTarget。
有条件地将原始未更改的值和新修改的值都馈送到.content.offset(x:y :)修饰符中-取决于dragState作为缺少的scrollDelegate方法的替代。
仅在拖动结束时才有条件地添加动画。
将手势附加到scrollView。
长话短说。没用 我希望我能解决我的问题。
有什么解决办法吗?期待任何投入。谢谢!
guj*_*jci 21
我设法通过@Binding索引实现了分页行为。该解决方案可能看起来很脏,我将解释我的解决方法。
我做错的第一件事是对齐.leading而不是默认值.center,否则偏移量会不正常。然后我结合了绑定和本地偏移状态。这有点违反“单一事实来源”原则,但除此之外我不知道如何处理外部索引更改并修改我的偏移量。
所以,我的代码如下
struct SwiftUIPagerView<Content: View & Identifiable>: View {
@Binding var index: Int
@State private var offset: CGFloat = 0
@State private var isGestureActive: Bool = false
// 1
var pages: [Content]
var body: some View {
GeometryReader { geometry in
ScrollView(.horizontal, showsIndicators: false) {
HStack(alignment: .center, spacing: 0) {
ForEach(self.pages) { page in
page
.frame(width: geometry.size.width, height: nil)
}
}
}
// 2
.content.offset(x: self.isGestureActive ? self.offset : -geometry.size.width * CGFloat(self.index))
// 3
.frame(width: geometry.size.width, height: nil, alignment: .leading)
.gesture(DragGesture().onChanged({ value in
// 4
self.isGestureActive = true
// 5
self.offset = value.translation.width + -geometry.size.width * CGFloat(self.index)
}).onEnded({ value in
if -value.predictedEndTranslation.width > geometry.size.width / 2, self.index < self.pages.endIndex - 1 {
self.index += 1
}
if value.predictedEndTranslation.width > geometry.size.width / 2, self.index > 0 {
self.index -= 1
}
// 6
withAnimation { self.offset = -geometry.size.width * CGFloat(self.index) }
// 7
DispatchQueue.main.async { self.isGestureActive = false }
}))
}
}
}
Run Code Online (Sandbox Code Playgroud)
.leading 如果您不想将所有偏移量转换为中心,则是强制性的。我已经在以下上下文中对其进行了测试
struct WrapperView: View {
@State var index: Int = 0
var body: some View {
VStack {
SwiftUIPagerView(index: $index, pages: (0..<4).map { index in TODOView(extraInfo: "\(index + 1)") })
Picker(selection: self.$index.animation(.easeInOut), label: Text("")) {
ForEach(0..<4) { page in Text("\(page + 1)").tag(page) }
}
.pickerStyle(SegmentedPickerStyle())
.padding()
}
}
}
Run Code Online (Sandbox Code Playgroud)
TODOView我的自定义视图在哪里指示要实现的视图。
我希望我的问题是正确的,如果不是,请说明我应该关注哪个部分。我也欢迎任何删除isGestureActive状态的建议。
@gujci您的解决方案是完美的,对于更一般的用途,使其接受模型和视图生成器,如下所示(注意我在生成器中传递了几何尺寸):
struct SwiftUIPagerView<TModel: Identifiable ,TView: View >: View {
@Binding var index: Int
@State private var offset: CGFloat = 0
@State private var isGestureActive: Bool = false
// 1
var pages: [TModel]
var builder : (CGSize, TModel) -> TView
var body: some View {
GeometryReader { geometry in
ScrollView(.horizontal, showsIndicators: false) {
HStack(alignment: .center, spacing: 0) {
ForEach(self.pages) { page in
self.builder(geometry.size, page)
}
}
}
// 2
.content.offset(x: self.isGestureActive ? self.offset : -geometry.size.width * CGFloat(self.index))
// 3
.frame(width: geometry.size.width, height: nil, alignment: .leading)
.gesture(DragGesture().onChanged({ value in
// 4
self.isGestureActive = true
// 5
self.offset = value.translation.width + -geometry.size.width * CGFloat(self.index)
}).onEnded({ value in
if -value.predictedEndTranslation.width > geometry.size.width / 2, self.index < self.pages.endIndex - 1 {
self.index += 1
}
if value.predictedEndTranslation.width > geometry.size.width / 2, self.index > 0 {
self.index -= 1
}
// 6
withAnimation { self.offset = -geometry.size.width * CGFloat(self.index) }
// 7
DispatchQueue.main.async { self.isGestureActive = false }
}))
}
}
}
Run Code Online (Sandbox Code Playgroud)
并可以用作:
struct WrapperView: View {
@State var index: Int = 0
@State var items : [(color:Color,name:String)] = [
(.red,"Red"),
(.green,"Green"),
(.yellow,"Yellow"),
(.blue,"Blue")
]
var body: some View {
VStack(spacing: 0) {
SwiftUIPagerView(index: $index, pages: self.items.identify { $0.name }) { size, item in
TODOView(extraInfo: item.model.name)
.frame(width: size.width, height: size.height)
.background(item.model.color)
}
Picker(selection: self.$index.animation(.easeInOut), label: Text("")) {
ForEach(0..<4) { page in Text("\(page + 1)").tag(page) }
}
.pickerStyle(SegmentedPickerStyle())
}.edgesIgnoringSafeArea(.all)
}
}
Run Code Online (Sandbox Code Playgroud)
在一些实用程序的帮助下:
struct MakeIdentifiable<TModel,TID:Hashable> : Identifiable {
var id : TID {
return idetifier(model)
}
let model : TModel
let idetifier : (TModel) -> TID
}
extension Array {
func identify<TID: Hashable>(by: @escaping (Element)->TID) -> [MakeIdentifiable<Element, TID>]
{
return self.map { MakeIdentifiable.init(model: $0, idetifier: by) }
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
2173 次 |
| 最近记录: |