我们需求是想要实现tableView的 willDisplay 方法,当View出现在屏幕上是,进行埋点,
需要在 scrollViewDidScroll 方法里实现。
1、当时同事提了一个方案,就是每次来这个方法,就去遍历子视图,那些在屏幕内,然后根据与上次的状态对比,可以判断是离开还是刚进入屏幕或者是暂时没变化。
可是因为 scrollViewDidScroll 方法调用特别频繁,然后再在里面做for循环遍历子视图,我遍思考别的可行性方案,于是有了第二种
2、我们的需求是,第一次显示在屏幕内,去埋点,那么我忽略掉离开屏幕的子视图,只往下判断,那两个值去记录最新出现在屏幕上的试图为 bottomWidget,当用户往上滑的时候,判断下一个试图是否在屏幕内,如果在,替换bottomWidget,同时我做了一个距离差,我们的每个子视图的高度至少为 114,那么当滑动偏移量距特别小的时候,也就是用户滑动特别慢的时候,无需过多冗余计算,于是我保存了一个 minOffset = 44,得才以下无需遍历的优化代码:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
judgementViewIsVisitable(contentOffset: scrollView.contentOffset)
}
func judgementViewIsVisitable(contentOffset: CGPoint) {
if let lastContentOffsetY = lastContentOffsetY {
let offsetY = contentOffset.y - lastContentOffsetY
if abs(offsetY) < minOffset {
return
}
self.lastContentOffsetY = contentOffset.y
if offsetY < 0 { // 往下滑, 需要替换topWidget 暂时不处理
} else { // 往上滑,替换 bottomWidget,只处理之前没看过的
if let index = firstVisibleViewDic.firstIndex(where: { model in
model == bottomWidget
}) {
if index == firstVisibleViewDic.count - 1 {
return
}
// 判断下一个
handleCurrentModel(model: firstVisibleViewDic[index + 1])
}
}
} else {
lastContentOffsetY = contentOffset.y
}
}
func handleCurrentModel(model: VisiableWidgetModel) {
if model.isVisiable == false {
if let view = widgetViewDict[model.widgetId] as? UIView, !view.isHidden, view.intersectsOtherView(otherView: canVisitableView) {
model.isVisiable = true
bottomWidget = model
perfomViewCanVisited(model: model)
} else if let vc = widgetViewDict[model.widgetId] as? UIViewController, vc.view.isHidden, vc.view.intersectsOtherView(otherView: canVisitableView) {
model.isVisiable = true
bottomWidget = model
perfomViewCanVisited(model: model)
}
}
}
func perfomViewCanVisited(model: VisiableWidgetModel) {
// 处理事件
}
判断是否在屏幕内的代码如下:
// view and view relationship
public extension UIView {
enum RectRelationType: Int {
case intersects
case contains
}
func intersectsOtherView(otherView: UIView? = nil) -> Bool {
return relationToOtherView(otherView: otherView, relationType: .intersects)
}
func containsOtherView(otherView: UIView? = nil) -> Bool {
return relationToOtherView(otherView: otherView, relationType: .contains)
}
private func relationToOtherView(otherView: UIView? = nil, relationType: RectRelationType) -> Bool {
var window = otherView
if window == nil {
if #available(iOS 13.0, *) {
for windowScene: UIWindowScene in (UIApplication.shared.connectedScenes as? Set)! {
window = windowScene.windows.first
break
}
} else {
window = UIApplication.shared.keyWindow
}
}
let selfRect = self.convert(self.bounds, to: nil)
if let `window` = window {
let otherRect = window.convert(window.bounds, to: nil)
switch relationType {
case .intersects:
return selfRect.intersects(otherRect)
case .contains:
return selfRect.contains(otherRect)
}
}
return false
}
}