SwiftUI实现下拉时Header置顶并拉伸效果的技术求助
SwiftUI实现下拉ScrollView时顶部Header固定且图片可拉伸的交互效果
需求:复刻Gentler Streaks应用的交互——下拉ScrollView时,顶部包含两张图片的Header固定在视图顶部,同时背景图片可随下拉动作拉伸。
当前问题:Header随ScrollView一起滚动,下拉时没有图片拉伸效果。
以下是修改后的完整代码,实现所需交互:
import SwiftUI import MapKit struct PredatorDetail: View { let predator: ApexPredator @State var position: MapCameraPosition // 记录滚动偏移量 @State private var scrollOffset: CGFloat = 0 // Header基础高度 private let baseHeaderHeight: CGFloat = 280 var body: some View { GeometryReader { rootGeo in ZStack(alignment: .top) { // 固定在顶部的Header HeaderView(predator: predator, scrollOffset: scrollOffset, baseHeight: baseHeaderHeight, screenWidth: rootGeo.size.width) ScrollView { LazyVStack(alignment: .leading, spacing: 0) { // 占位空间,避免内容被Header遮挡 Color.clear.frame(height: baseHeaderHeight) //dino name Text(predator.name) .font(.largeTitle) //current location NavigationLink { PredatorMap(position: .camera(MapCamera(centerCoordinate: predator.location, distance: 1000, heading: 250, pitch: 80))) } label: { Map(position: $position) { Annotation(predator.name, coordinate: predator.location) { Image(systemName: "mappin.and.ellipse") .font(.largeTitle) .imageScale(.large) .symbolEffect(.pulse) } .annotationTitles(.hidden) } .frame(height: 125) .overlay(alignment: .trailing) { Image(systemName: "greaterthan") .imageScale(.large) .font(.title3) .padding(.trailing, 5) } .overlay(alignment: .topLeading) { Text("Current Location") .padding([.leading, .bottom], 5) .padding(.trailing, 8) .background(.black.opacity(0.7)) .clipShape(.rect(bottomTrailingRadius:15)) } .clipShape(.rect(cornerRadius: 15)) } // apears in Text("Appears In:") .font(.title3) ForEach(predator.movies, id: \.self) { movie in Text("•" + movie) .font(.subheadline) } // move momens Text("Movie Moments:") .font(.title) .padding(.top, 15) ForEach(predator.movieScenes) { scene in Text(scene.movie) .font(.title2) .padding(.vertical, 1) Text(scene.sceneDescription) .padding(.bottom, 15) } Text("Read More") .font(.caption) Link(predator.link, destination: URL(string: predator.link)!) .font(.caption) .foregroundStyle(.blue) } .padding() .padding(.bottom, 30) .frame(width: rootGeo.size.width, alignment: .leading) // 监听滚动偏移 .background( GeometryReader { geo in Color.clear .preference(key: ScrollOffsetKey.self, value: geo.frame(in: .named("scrollView")).minY) } ) } .coordinateSpace(name: "scrollView") .ignoresSafeArea() } .onPreferenceChange(ScrollOffsetKey.self) { value in // 只处理下拉的偏移量(value > 0表示下拉) scrollOffset = max(value, 0) } } .toolbarBackground(.automatic) } } // 自定义Header视图 struct HeaderView: View { let predator: ApexPredator let scrollOffset: CGFloat let baseHeight: CGFloat let screenWidth: CGFloat var body: some View { ZStack (alignment: .bottomTrailing) { // 背景图片,随下拉拉伸 Image(predator.type.rawValue) .resizable() .scaledToFill() // 计算拉伸后的高度:基础高度 + 滚动偏移量 .frame(height: baseHeight + scrollOffset) // 保持宽度充满屏幕,拉伸时居中显示 .frame(width: screenWidth) .overlay { LinearGradient(stops: [Gradient.Stop(color: .clear, location: 0.8), Gradient.Stop(color: .black, location: 1)], startPoint: .top, endPoint: .bottom) } .clipped() // 恐龙图片,固定位置(下拉时仅背景图拉伸) Image(predator.image) .resizable() .scaledToFit() .frame(width: screenWidth/1.5, height: baseHeight * 0.7) .scaleEffect(x: -1) .shadow(color: .black, radius: 7) // 固定在Header底部,不随滚动偏移移动 .offset(y: baseHeight * 0.1) } // 固定Header的基础高度,下拉时高度动态增加 .frame(height: baseHeight + scrollOffset) } } // 自定义PreferenceKey监听滚动偏移 struct ScrollOffsetKey: PreferenceKey { static var defaultValue: CGFloat = 0 static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { value = nextValue() } } #Preview { PredatorDetail(predator: Predators().apexPredators[10], position: .camera(MapCamera(centerCoordinate: Predators().apexPredators[10].location, distance: 30000))) .preferredColorScheme(.dark) }
关键实现说明:
- 滚动偏移监听:通过
GeometryReader和自定义PreferenceKey获取ScrollView的滚动偏移量,仅处理下拉(偏移量为正)的情况。 - Header固定:将Header放在ZStack的顶部层级,独立于ScrollView,实现固定效果。
- 图片拉伸:背景图片的高度随滚动偏移量动态增加,使用
scaledToFill保证图片拉伸时充满屏幕,同时保持居中显示。 - 内容占位:在ScrollView最顶部添加与Header基础高度一致的透明占位,避免内容被Header遮挡。
内容的提问来源于stack exchange,提问作者eatom
相关产品推荐
相关产品推荐

