如何将 ScrollView 中调整大小的 View 锚定到顶部引导点,以便它们在增长时向下增长?
上下文:我正在尝试制作一个 View 的 ScrollView ,我可以通过上下拖动底部的 handle 来调整高度。我的问题是,当它调整大小时,它会上下调整大小。我希望顶部保持不动,只调整它向下移动的距离。
我认为问题不在于 ScrollView ,因为如果我用 VStack 替换它,行为是相同的。但是,在 ScrollView 的上下文中,向上调整大小会使用户无法向上滚动到足以看到 View 顶部的程度。
完整的示例代码位于屏幕截图下方。 iPad 和 iPhone 模拟器都有这个问题
在下面的屏幕截图中, ScrollView 都滚动到顶部。第一个屏幕截图显示了调整最顶部项目大小之前的开始状态
第二个屏幕截图显示了调整最顶部项目的大小后的状态 - 现在最顶部的项目超出了列表,因此我们看不到向上滚动以查看顶部
下面是完整代码,可在 Xcode 11.0 上运行,以显示问题
struct ScaleItem: View {
static let defaultHeight: CGFloat = 240.0
@State var heightDiff: CGFloat = 0.0
@State var currentHeight: CGFloat = ScaleItem.defaultHeight
var resizingButton: some View {
VStack {
VStack {
Spacer(minLength: 15)
HStack {
Spacer()
Image(systemName: "arrow.up.and.down.square")
.background(Color.white)
Spacer()
}
}
Spacer()
.frame(height: 11)
}
.background(Color.clear)
}
var body: some View {
ZStack {
VStack {
Spacer()
HStack {
Spacer()
Text("Sample")
Spacer()
}
Spacer()
}
.background(Color.red)
.overlay(
RoundedRectangle(cornerRadius: 5.0)
.strokeBorder(Color.black, lineWidth: 1.0)
.shadow(radius: 3.0)
)
.padding()
.frame(
minHeight: self.currentHeight + heightDiff,
idealHeight: self.currentHeight + heightDiff,
maxHeight: self.currentHeight + heightDiff,
alignment: .top
)
resizingButton
.gesture(
DragGesture()
.onChanged({ gesture in
print("Changed")
let location = gesture.location
let startLocation = gesture.startLocation
let deltaY = location.y - startLocation.y
self.heightDiff = deltaY
print(deltaY)
})
.onEnded { gesture in
print("Ended")
let location = gesture.location
let startLocation = gesture.startLocation
let deltaY = location.y - startLocation.y
self.currentHeight = max(ScaleItem.defaultHeight, self.currentHeight + deltaY)
self.heightDiff = 0
print(deltaY)
print(String(describing: gesture))
})
}
}
}
struct ScaleDemoView: View {
var body: some View {
ScrollView {
ForEach(0..<3) { _ in
ScaleItem()
}
}
}
}
最佳答案
解决此问题的一种方法是在拖动过程中将 ScrollView 重绘到其原始位置。创建一个观察者对象,当您的 DeltaY 发生变化时,将通知父 View 并相应地更新 View 。
- 首先,创建一个ObservableObject
final class DeltaHeight: ObservableObject
并传递给 subview 。 - 将
.offset(x: 0, y: 0)
添加到您的 ScrollView
测试代码如下:
import SwiftUI
struct ScaleDemoView_Previews: PreviewProvider {
static var previews: some View {
ScaleDemoView()
}
}
struct ScaleItem: View {
@ObservedObject var delta: DeltaHeight
static let defaultHeight: CGFloat = 200.0
@State var heightDiff: CGFloat = 0.0
@State var currentHeight: CGFloat = ScaleItem.defaultHeight
var resizingButton: some View {
VStack {
VStack {
Spacer(minLength: 15)
HStack {
Spacer()
Image(systemName: "arrow.up.and.down.square")
.background(Color.white)
Spacer()
}
}
Spacer()
.frame(height: 11)
}
.background(Color.clear)
}
var body: some View {
ZStack {
VStack {
Spacer()
HStack {
Spacer()
Text("Sample")
Spacer()
}
Spacer()
}
.background(Color.red)
.overlay(
RoundedRectangle(cornerRadius: 5.0)
.strokeBorder(Color.black, lineWidth: 1.0)
.shadow(radius: 3.0)
)
.padding()
.frame(
minHeight: self.currentHeight + heightDiff,
idealHeight: self.currentHeight + heightDiff,
maxHeight: self.currentHeight + heightDiff,
alignment: .top
)
resizingButton
.gesture(
DragGesture()
.onChanged({ gesture in
print("Changed")
let location = gesture.location
let startLocation = gesture.startLocation
let deltaY = location.y - startLocation.y
self.heightDiff = deltaY
print("deltaY: ", deltaY)
self.delta.delta = deltaY
})
.onEnded { gesture in
print("Ended")
let location = gesture.location
let startLocation = gesture.startLocation
let deltaY = location.y - startLocation.y
self.currentHeight = max(ScaleItem.defaultHeight, self.currentHeight + deltaY)
self.heightDiff = 0
print(deltaY)
print(String(describing: gesture))
})
}
}
}
struct ScaleDemoView: View {
@ObservedObject var delta = DeltaHeight()
var body: some View {
ScrollView(.vertical) {
ForEach(0..<3) { _ in
ScaleItem(delta: self.delta)
}
}.offset(x: 0, y: 0)
.background(Color.green)
}
}
final class DeltaHeight: ObservableObject {
@Published var delta: CGFloat = 0.0
}
关于ios - SwiftUI: ScrollView 中的顶部锚定可调整大小的 View ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58242823/