SwiftUI 实现依附在顶部标题栏的视图

发布
更新
字数 249
阅读 2 分钟
阅读量 605

通过两层 GeometryReader 读取 ScrollView 内容及置顶视图的位置信息进行处理,可以实现在滚动时,置顶内容固定在顶部,并随着导航标题栏大小标题样式切换而自动适应位置。同时为置顶内容增加了阴影效果,会随着上下滚动深浅变化。

import SwiftUI

struct AffixTop: ViewModifier {
    var selfGeometry: GeometryProxy
    var geometry: GeometryProxy
    var always: Bool
    
    private var offsetY: CGFloat {
        always ? geometry.frame(in: .global).minY - selfGeometry.frame(in: .global).minY : max(0, geometry.frame(in: .global).minY - selfGeometry.frame(in: .global).minY)
    }
    
    func body(content: Content) -> some View {
        content
            .offset(y: offsetY)
            .shadow(
                color: Color(.sRGBLinear, white: 0, opacity: min(offsetY / 40 * 0.12, 0.12)),
                radius: min(offsetY / 10, 4),
                y: min(offsetY / 10, 4)
            )
    }
}

extension View {
    func affixTop(_ selfGeometry: GeometryProxy, outer geometry: GeometryProxy, always: Bool = false) -> some View {
        modifier(AffixTop(selfGeometry: selfGeometry, geometry: geometry, always: always))
    }
}

struct AffixTop_Previews: PreviewProvider {
    static var previews: some View {
        NavigationView {
            GeometryReader { geometry in
                ScrollView {
                    VStack {
                        Text("SwiftDict").frame(width: geometry.size.width, height: 400)
                        Text("swiftdict.com").frame(width: geometry.size.width, height: 500).background(.orange)
                    }
                    .overlay {
                        GeometryReader { selfGeometry in
                            Text("Affix").frame(width: geometry.size.width, height: 44).background().affixTop(selfGeometry, outer: geometry, always: true)
                        }
                    }
                }
            }
            .navigationTitle("Affix top")
            .navigationBarTitleDisplayMode(.inline)
        }
    }
}