You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在SwiftUI中按百分比追踪视图可见度

计算SwiftUI视图的可见百分比

核心思路

要计算ExampleView的可见占比,需要获取两个关键矩形区域:

  • ExampleView在全局坐标系下的完整frame
  • 滚动容器(List)的可见区域frame
    通过计算两者的交集面积与ExampleView总面积的比值,即可得到可见百分比。

纯SwiftUI实现方案

1. 定义PreferenceKey传递视图全局frame

首先创建一个PreferenceKey,用于将ExampleView的全局frame传递给父视图:

struct ViewFramePreferenceKey: PreferenceKey {
    static var defaultValue: CGRect = .zero
    static func reduce(value: inout CGRect, nextValue: () -> CGRect) {
        value = nextValue()
    }
}

2. 修改ExampleView传递自身全局frame

在ExampleView的背景中嵌入GeometryReader,获取其在全局坐标系下的frame并通过Preference传递:

struct ExampleView: View {
    var body: some View {
        ZStack {
            Color.yellow
            Text("Hello World !!!")
        }
        .background(
            GeometryReader { geo in
                Color.clear
                    .preference(key: ViewFramePreferenceKey.self, value: geo.frame(in: .global))
            }
        )
    }
}

3. 在ContentView中计算可见百分比

在父视图中监听List的可见区域和ExampleView的frame变化,实时计算交集占比:

struct ContentView: View {
    @State private var listVisibleFrame: CGRect = .zero
    @State private var exampleViewFrame: CGRect = .zero
    @State private var visibilityPercentage: CGFloat = 0
    
    var body: some View {
        NavigationView {
            List {
                ForEach(1..<20) { index in
                    if index % 5 == 0 {
                        VStack {
                            ExampleView()
                                .onPreferenceChange(ViewFramePreferenceKey.self) { frame in
                                    exampleViewFrame = frame
                                    calculateVisibility()
                                }
                        }
                        .frame(minHeight: 500)
                    } else {
                        Rectangle()
                            .fill(.blue)
                            .frame(minHeight: 300)
                    }
                }
            }
            // 获取List的可见区域frame
            .background(
                GeometryReader { geo in
                    Color.clear
                        .onAppear {
                            listVisibleFrame = geo.frame(in: .global)
                        }
                        .onChange(of: geo.frame(in: .global)) { newFrame in
                            listVisibleFrame = newFrame
                            calculateVisibility()
                        }
                }
            )
            .navigationTitle("Visibility Demo")
            // 实时显示可见百分比
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    Text(String(format: "%.1f%%", visibilityPercentage * 100))
                }
            }
        }
    }
    
    private func calculateVisibility() {
        // 确保视图已加载,frame有效
        guard exampleViewFrame.width > 0 && exampleViewFrame.height > 0 else {
            visibilityPercentage = 0
            return
        }
        
        // 计算两个矩形的交集区域
        let intersection = exampleViewFrame.intersection(listVisibleFrame)
        let intersectionArea = intersection.width * intersection.height
        let totalArea = exampleViewFrame.width * exampleViewFrame.height
        
        // 计算占比,确保值非负
        visibilityPercentage = max(intersectionArea / totalArea, 0)
        
        // 可选:仅当视图首次变为可见时开始统计
        // if visibilityPercentage == 0 && newPercentage > 0 {
        //     // 这里可以添加统计开始的逻辑,比如记录时间
        // }
    }
}

基于UIKit的备选方案(更精准的滚动监听)

如果需要更精准的滚动状态监听,可以利用SwiftUI List底层的UIScrollView代理:

  1. 创建一个UIViewRepresentable组件监听滚动视图的可见区域变化
  2. 在scrollViewDidScroll回调中实时更新可见区域frame
  3. 后续计算逻辑与纯SwiftUI方案一致

这种方法适合需要处理复杂滚动场景(如缩放、快速滚动)的情况,但依赖UIKit层级,兼容性略低于纯SwiftUI方案。

内容的提问来源于stack exchange,提问作者V D Purohit

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 08:52:55