SwiftUI:滚动视图中的顶部锚定可调整大小视图
Posted
技术标签:
【中文标题】SwiftUI:滚动视图中的顶部锚定可调整大小视图【英文标题】:SwiftUI: top-anchoring resizable views in a scroll view 【发布时间】:2019-10-04 20:24:55 【问题描述】:如何将滚动视图中调整大小的视图锚定到顶部引导点,以便它们在增长时向下增长?
上下文:我正在尝试制作视图的滚动视图,我可以通过上下拖动底部的手柄来调整高度。我的问题是,当它调整大小时,它的大小同样会向上和向下调整。我希望顶部保持不动,只调整它向下的距离。
我认为问题不在于滚动视图,因为如果我用 VStack 替换它,行为是相同的。但是,在滚动视图的上下文中,它向上调整大小使用户无法向上滚动足够远以看到视图的顶部。
完整的示例代码如下截图。问题出在 iPad 和 iPhone 模拟器上
在以下屏幕截图中,滚动视图都滚动到顶部。第一个屏幕截图显示了调整最顶部项目大小之前的开始状态
第二个屏幕截图显示了调整最顶部项目的大小后的状态 - 最顶部的项目现在超出了列表,因此我们无法看到向上滚动以查看顶部
下面是完整的代码,可以用 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()
【问题讨论】:
【参考方案1】:解决此问题的一种方法是在拖动过程中将 ScrollView 重新绘制到其原始位置。创建一个观察者对象,当您的 DeltaY 发生更改时,将通知父视图并相应地更新视图。
首先,创建一个 ObservableObjectfinal class DeltaHeight: ObservableObject
并传递给子视图。
将.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
【讨论】:
谢谢! :-) 这样可行。 :-) 我唯一需要添加的就是在 onEnded 中更新 self.delta.delta,否则当它被调整为小于 ScaleItem.defaultHeight 时会出现类似的跳跃。非常感谢你的帮助! :-) 我猜想在 scrollView 后面,ScrollView 就像 reloadRows tableViewCell 一样工作,当你改变高度时,视图会根据用户的最佳可见性而增长。以上是关于SwiftUI:滚动视图中的顶部锚定可调整大小视图的主要内容,如果未能解决你的问题,请参考以下文章
如何向 swiftUI ScrollView 指示内容大小已更改?