好吧,老实说,我做到了,因为我需要它,然后环顾四周,没有在 SwiftUI 中找到任何原生的东西,所以想分享。因此,这只是一个自我回答的问题。
最初,我需要粘性可拉伸粘性标题,用于仅依赖于
ScrollView
的惰性内容。
后来(在我得到解决方案后)我在 Medium 上发现了这个,但我不喜欢它(并且至少不会推荐原样),因为:
offset
(我不喜欢使用offset,因为它与布局不一致等)
SwiftUI 免费为我们提供的初始代码
ScrollView {
LazyVStack(spacing: 8, pinnedViews: [.sectionHeaders]) {
Section {
ForEach(0...100) {
Text("Item \($0)")
.frame(maxWidth: .infinity, minHeight: 60)
}
} header: {
Image("picture").resizable().scaledToFill()
.frame(height: 200)
}
}
}
标题向上滚动时会粘住,但向下滚动(随内容拖动)时不会粘住,并且不可拉伸。
ScrollView
的顶部
ScrollView
现在可以私下管理内容偏移量(UIKit 变体不在此处的主题范围内),因此可以使用覆盖层固定到顶部
ScrollView {
// ...
}
.overlay(
// >> any header
Image("picture").resizable().scaledToFill()
// << header end
.frame(height: imageHeight) // will calculate below
.clipped()
Section
默认标题(作为占位符)来计算距
ScrollView
顶部的当前距离
Section(...) {
// ...
} header: {
// here is only caculable part
GeometryReader {
// detect current position of header bottom edge
Color.clear.preference(key: ViewOffsetKey.self,
value: $0.frame(in: .named("area")).maxY)
}
.frame(height: headerHeight)
.onPreferenceChange(ViewOffsetKey.self) {
// prevent image negative height if header is not pinned
// for simplicity (can be optional, etc.)
imageHeight = $0 < 0 ? 0.001 : $0
}
}
使用 Xcode 13.4 / iOS 15.5 进行测试
ScrollView的解决方案:
1/找到scrollOffset(参见示例或使用ScrollViewWithScrollOffset)
2/ 使用GeometryReader 包裹图像,以避免帧故障并在设备屏幕上获得正常的图像大小
3/使用GeometryReader中的尺寸和scrollOffset来设置图像框架完整代码:
struct ContentView: View {
@State var scrollOffset: CGFloat = 0
private let coordinateSpaceName = "scrollViewSpaceName"
var body: some View {
ScrollView {
VStack {
image
Color.gray.frame(height: 1000)
}
.background( // 1. find scrollOffset
GeometryReader { proxy in
let offset = proxy.frame(in: .named(coordinateSpaceName)).minY
Color.clear.preference(key: ScrollViewWithPullDownOffsetPreferenceKey.self, value: offset)
}
)
}
.coordinateSpace(name: coordinateSpaceName)
.onPreferenceChange(ScrollViewWithPullDownOffsetPreferenceKey.self) { value in
scrollOffset = value
}
}
var image: some View { frame
GeometryReader { proxy in // 2. get actual size on screen
Image(systemName: "heart.fill") // 3. use scrollOffset to adjust image
.resizable()
.aspectRatio(contentMode: .fit)
.padding(.horizontal, min(0, -scrollOffset))
.frame(width: proxy.size.width,
height: proxy.size.height + max(0, scrollOffset))
.offset(CGSize(width: 0, height: min(0, -scrollOffset)))
}
.aspectRatio(CGSize(width: 375, height: 280), contentMode: .fit)
}
}