在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代理:
- 创建一个
UIViewRepresentable组件监听滚动视图的可见区域变化 - 在
scrollViewDidScroll回调中实时更新可见区域frame - 后续计算逻辑与纯SwiftUI方案一致
这种方法适合需要处理复杂滚动场景(如缩放、快速滚动)的情况,但依赖UIKit层级,兼容性略低于纯SwiftUI方案。
内容的提问来源于stack exchange,提问作者V D Purohit
相关产品推荐
相关产品推荐

