自定义基于手势的向上滑动卡中的 ScrollView 在 SwiftUI 中导致问题
ScrollView inside custom gesture-based slide-up card causing issues in SwiftUI
我已经制作了一个最小的可重现示例,我将在下面粘贴。只需将其插入 Xcode,您就会明白这是怎么回事。本质上,我有一张定制的滑盖卡片。当它的枚举位置是 .top 时,我在 Xcode 模拟器中滑动 ScrollView,它会导致卡片的位置稍微移动。有什么办法可以锁定卡片的位置?或者至少让滑动卡片中的 ScrollView 减少滑动手势的问题?
内容视图:
struct ContentView : View {
var body: some View {
ZStack(alignment: Alignment.top) {
Text("test")
.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
.background(Color.red)
SlideOverCard {
VStack {
ScrollView {
VStack {
ForEach(1..<100) { _ in
Text("test")
}
}.frame(width: UIScreen.main.bounds.width)
}
Text("TESTER LINE OF TEXT")
Spacer()
}.frame(width: UIScreen.main.bounds.width)
}
}
.edgesIgnoringSafeArea(.vertical)
}
}
滑动卡片:
struct SlideOverCard<Content: View> : View {
@GestureState private var dragState = DragState.inactive
@State var position = CardPosition.top
var content: () -> Content
var body: some View {
let drag = DragGesture()
.updating($dragState) { drag, state, transaction in
state = .dragging(translation: drag.translation)
}
.onEnded(onDragEnded)
return Group {
Handle()
self.content()
}
.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
.background(Color.white)
.cornerRadius(10.0)
.shadow(color: Color(.sRGBLinear, white: 0, opacity: 0.13), radius: 10.0)
.offset(y: self.position.rawValue + self.dragState.translation.height)
.animation(self.dragState.isDragging ? nil : .interpolatingSpring(stiffness: 300.0, damping: 30.0, initialVelocity: 10.0))
.gesture(drag)
}
private func onDragEnded(drag: DragGesture.Value) {
let verticalDirection = drag.predictedEndLocation.y - drag.location.y
let cardTopEdgeLocation = self.position.rawValue + drag.translation.height
let positionAbove: CardPosition
let positionBelow: CardPosition
let closestPosition: CardPosition
if cardTopEdgeLocation <= CardPosition.middle.rawValue {
positionAbove = .top
positionBelow = .middle
} else {
positionAbove = .middle
positionBelow = .bottom
}
if (cardTopEdgeLocation - positionAbove.rawValue) < (positionBelow.rawValue - cardTopEdgeLocation) {
closestPosition = positionAbove
} else {
closestPosition = positionBelow
}
if verticalDirection > 0 {
self.position = positionBelow
} else if verticalDirection < 0 {
self.position = positionAbove
} else {
self.position = closestPosition
}
}
}
enum CardPosition: CGFloat {
case top = 100
case middle = 500
case bottom = 850
}
enum DragState {
case inactive
case dragging(translation: CGSize)
var translation: CGSize {
switch self {
case .inactive:
return .zero
case .dragging(let translation):
return translation
}
}
var isDragging: Bool {
switch self {
case .inactive:
return false
case .dragging:
return true
}
}
}
句柄:
struct Handle : View {
private let handleThickness = CGFloat(5.0)
var body: some View {
RoundedRectangle(cornerRadius: handleThickness / 2.0)
.frame(width: 40, height: handleThickness)
.foregroundColor(Color.secondary)
.padding(5)
}
}
好的,我成功地重现了你的意思...
测试 Xcode 12 / iOS 14
更新: - 这里找到解决方案
ScrollView {
VStack {
ForEach(1..<100) { _ in
Text("test")
}
}.frame(width: UIScreen.main.bounds.width)
}
.background(Color.white) // << make opaque background
.highPriorityGesture(DragGesture()) // << block below DragGesture
另外,我会考虑在“手柄”上移动拖动手势的变体(因为您已经有了)
return VStack { // << make it instead of Group
Handle()
.gesture(drag) // << here !!
self.content()
}
.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
.background(Color.white)
.cornerRadius(10.0)
.shadow(color: Color(.sRGBLinear, white: 0, opacity: 0.13), radius: 10.0)
.offset(y: self.position.rawValue + self.dragState.translation.height)
.animation(self.dragState.isDragging ? nil : .interpolatingSpring(stiffness: 300.0, damping: 30.0, initialVelocity: 10.0))
我已经制作了一个最小的可重现示例,我将在下面粘贴。只需将其插入 Xcode,您就会明白这是怎么回事。本质上,我有一张定制的滑盖卡片。当它的枚举位置是 .top 时,我在 Xcode 模拟器中滑动 ScrollView,它会导致卡片的位置稍微移动。有什么办法可以锁定卡片的位置?或者至少让滑动卡片中的 ScrollView 减少滑动手势的问题?
内容视图:
struct ContentView : View {
var body: some View {
ZStack(alignment: Alignment.top) {
Text("test")
.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
.background(Color.red)
SlideOverCard {
VStack {
ScrollView {
VStack {
ForEach(1..<100) { _ in
Text("test")
}
}.frame(width: UIScreen.main.bounds.width)
}
Text("TESTER LINE OF TEXT")
Spacer()
}.frame(width: UIScreen.main.bounds.width)
}
}
.edgesIgnoringSafeArea(.vertical)
}
}
滑动卡片:
struct SlideOverCard<Content: View> : View {
@GestureState private var dragState = DragState.inactive
@State var position = CardPosition.top
var content: () -> Content
var body: some View {
let drag = DragGesture()
.updating($dragState) { drag, state, transaction in
state = .dragging(translation: drag.translation)
}
.onEnded(onDragEnded)
return Group {
Handle()
self.content()
}
.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
.background(Color.white)
.cornerRadius(10.0)
.shadow(color: Color(.sRGBLinear, white: 0, opacity: 0.13), radius: 10.0)
.offset(y: self.position.rawValue + self.dragState.translation.height)
.animation(self.dragState.isDragging ? nil : .interpolatingSpring(stiffness: 300.0, damping: 30.0, initialVelocity: 10.0))
.gesture(drag)
}
private func onDragEnded(drag: DragGesture.Value) {
let verticalDirection = drag.predictedEndLocation.y - drag.location.y
let cardTopEdgeLocation = self.position.rawValue + drag.translation.height
let positionAbove: CardPosition
let positionBelow: CardPosition
let closestPosition: CardPosition
if cardTopEdgeLocation <= CardPosition.middle.rawValue {
positionAbove = .top
positionBelow = .middle
} else {
positionAbove = .middle
positionBelow = .bottom
}
if (cardTopEdgeLocation - positionAbove.rawValue) < (positionBelow.rawValue - cardTopEdgeLocation) {
closestPosition = positionAbove
} else {
closestPosition = positionBelow
}
if verticalDirection > 0 {
self.position = positionBelow
} else if verticalDirection < 0 {
self.position = positionAbove
} else {
self.position = closestPosition
}
}
}
enum CardPosition: CGFloat {
case top = 100
case middle = 500
case bottom = 850
}
enum DragState {
case inactive
case dragging(translation: CGSize)
var translation: CGSize {
switch self {
case .inactive:
return .zero
case .dragging(let translation):
return translation
}
}
var isDragging: Bool {
switch self {
case .inactive:
return false
case .dragging:
return true
}
}
}
句柄:
struct Handle : View {
private let handleThickness = CGFloat(5.0)
var body: some View {
RoundedRectangle(cornerRadius: handleThickness / 2.0)
.frame(width: 40, height: handleThickness)
.foregroundColor(Color.secondary)
.padding(5)
}
}
好的,我成功地重现了你的意思...
测试 Xcode 12 / iOS 14
更新: - 这里找到解决方案
ScrollView {
VStack {
ForEach(1..<100) { _ in
Text("test")
}
}.frame(width: UIScreen.main.bounds.width)
}
.background(Color.white) // << make opaque background
.highPriorityGesture(DragGesture()) // << block below DragGesture
另外,我会考虑在“手柄”上移动拖动手势的变体(因为您已经有了)
return VStack { // << make it instead of Group
Handle()
.gesture(drag) // << here !!
self.content()
}
.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
.background(Color.white)
.cornerRadius(10.0)
.shadow(color: Color(.sRGBLinear, white: 0, opacity: 0.13), radius: 10.0)
.offset(y: self.position.rawValue + self.dragState.translation.height)
.animation(self.dragState.isDragging ? nil : .interpolatingSpring(stiffness: 300.0, damping: 30.0, initialVelocity: 10.0))