iOS 客户端检查曝光组件 (Intersection Observer for iOS)

在客户端中如果需要实现曝光打点的需求,经常会遇到各种各样的问题,例如:该在什么时机去打点;复用的 view 打点混乱;切换界面或者切换 APP 前后台需不需要打点;等等。

ZHIntersectionObserver 就是为了解决这个问题而诞生的。

具体 Demo 演示效果可以先看进仓库查看:

Github地址: https://github.com/zhoon/ZHIntersectionObserver

热身:Intersection Observer API

说起 Intersection Observer,应该有些同学已经听说过了,在 Web 上已经有这样的一套 Web API 如下:https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API

Intersection Observer 的作用顾名思义就是监听 UI 元素是否跟某个父元素相交,常用于曝光监控。Web API 的 Intersection Observer 使用方式如下:

初始化一个 IntersectionObserver 并且传入一个回调 callback,和配置项 options(下文会做具体介绍):

设置需要监听的元素,然后开始监听:


当被监听元素跟 root 的相交(intersection)情况发生变化的时候,callback 就会被调用, 我们可以在 callback 里面处理一些业务,例如曝光打点等等,callback 会传回一个参数 entries 给业务判断当前的 intersection 情况:


延伸:Intersection Observer for iOS

作为 iOS 开发,虽然我们平时大部分业务都不需要曝光打点,但是当遇到一些这样的需求的时候,这个问题就变的比较棘手(例如一些推荐需求,推荐内容需要依赖客户端的曝光或者点击来实现动态推荐)。

我们平时在打曝光的时候,传统的做法一般是初始化某个 view 的时候去打个点,或者在 UITableView willDisplay 等 delegate 的时候去曝光,但是这些方法都有明显的缺点,例如:

没有比较精准的计算当前 view 是否真的在界面内导致曝光不准确

重复曝光,例如 UITableView 被多次 reload;

对复用的 View 不友好,例如 UITableViewCell;

曝光代码夹杂在各种业务代码中难维护;

无法控制曝光时间,快速滚动也会被当作曝光

除了以上一些比较明显的问题,可能还会有其他大大小小坑等着我们。

针对以上这些问题,Intersection Observer for iOS 诞生了:https://github.com/zhoon/ZHIntersectionObserver

参考 Intersection Observer 的 API,ZHIntersectionObserver 实现了在 iOS 平台的 Intersection Observer 功能,通过这些功能,我们可以方便的处理例如曝光打点的问题,ZHIntersectionObserver 的特点包括:

支持设置多个临界点(thresholds)

支持控制列表滚动检查曝光的频率(throttle)

View 被移除或者 hidden 或者 alpha 变化支持自动检查曝光

App 切换前台或者后台支持自动检查曝光

支持设置曝光时长(intersectionDuration)

支持数据变化自动检查曝光

兼容 UITableViewCell 等的复用控件

使用起来也非常简单,只需要初始化一个 IntersectionObserverContainerOptions 和 一个 IntersectionObserverTargetOptions 并且赋值给对应的 containerView 和 targetView 即可:

UIView*targetView=[[UIView alloc]init];

UIView*containerView=[[UIView alloc]init];

UIEdgeInsets rootMargin=UIEdgeInsetsMake(CGRectGetMaxY(self.navigationController.navigationBar.frame),0,0,0);

__weak__typeof(self)weakSelf=self;

IntersectionObserverContainerOptions*containerOptions=[IntersectionObserverContainerOptions initOptionsWithScope:@"Example1"rootMargin:rootMargin thresholds:@[@1]containerView:containerView intersectionDuration:300callback:^(NSString*_Nonnull scope,NSArray<IntersectionObserverEntry*>*_Nonnull entries){

__strong__typeof(weakSelf)strongSelf=weakSelf;

for(NSInteger i=0;i<entries.count;i++) {

    IntersectionObserverEntry*entry=entries[i];

    if (entry.isInsecting) {// 进入可是区域} else { // 移出可视区域}

}

}];

containerView.intersectionObserverContainerOptions=containerOptions;

IntersectionObserverTargetOptions*targetOptions=[IntersectionObserverTargetOptions initOptionsWithScope:@"Example1"targetView:targetView];

targetView.intersectionObserverTargetOptions=targetOptions;

参数详解(IntersectionObserverContainerOptions)

scope

作用域,一般一个 observer 对应一个作用域,某个作用域的 container 触发检查,只有对应作用域的 target 才会被通知。例如某个 UITableView 和它的 UITableViewCell 是同一个 scope,当列表滚动的时候,会自动发送检查事件,那么只有这个列表里面的 cell 才会做曝光检查。

rootMargin

控制父容器的边距,例如当我们把整个界面作为容器的时候,顶部可能有 navBar 或者底部有 tabBar,想要让 targetView 在可视区域才算曝光(也就是 navBar 之下和 tabBar 之上),那么就可以把 rootMargin 的 top 和 bottom 分别设置为 navBar 和 tabBar 的高度。

thresholds

设置临界点,有时有不想要整个 targetView 出现在可视区域才算曝光,而是有一像素出现或者某个面积的某个百分比,则可以设置 thresholds来实现,这个参数是一个数组,也就是某个设置的每个 threshold 零界点触发都会有 callback,默认值是 @[@1],也就是整个 targetView 进入可是区域才算曝光。

throttle

节流参数,对于 containerView 是 scrollView,ZHIntersectionObserver 会在滚动的时候自动去检查,节流参数可以控制检查的频率,避免频率太高影响性能。

intersectionDuration

曝光时间,这个是 web api 所没有支持的,即要求 targetView 需要曝光多长时间才会触发 callback。曝光问题里面有个比较难处理的就是要不要认为 targetView 需要在可视区域曝光一定的时长才算曝光,例如快速滚动的列表或者一闪而过的网络数据覆盖本地数据,这些能不能算曝光呢?intersectionDuration 可以控制 targetView 需要在可视区域停留一定时长才算曝光,默认 600 ms。

参数详解(IntersectionObserverTargetOptions)

scope

同 IntersectionObserverContainerOptions

dataKey

非常重要的一个参数,特别是对于复用的 view,例如 UITableViewCell。如果 view 复用,那么只能通过 dataKey 来区分当前是不同的数据,当某个 view 被复用了,需要 update 一下当前 view 所对应的 IntersectionObserverTargetOptions 对应的 dataKey,值一般是当前数据的 id 或者组合字符串,标记当前的数据独一无二。

data

当前 dataKey 对应的 data,ZHIntersectionObserver 不会使用,只会在 IntersectionObserverEntry 里面透传给 callback,方便业务使用。

原理及思考

1、所有的曝光都是基于 containerView 和 targetView 的,也就是说我们检测的是 targetView 在 containerView 容器中的曝光情况,而不是相对于当前应用界面的 window。如果是你的 containerView 也发生位置或者大小的变化,那应该通过同坐 roomMargin 来响应这种变化。

2、什么时机触发曝光检查?当 containerView 和 targetView 被指定一个 options 的时候,会自动触发一次曝光检查,然后会自动给 view 绑定一个 KVO,监听 view 的 alpha、hidden、bounds、position 的变化,只要这些属性变化,都会重新检查曝光。另外,一般我们一个 containerView 就对应一个 observer,所以当属性变化的时候,只会触发 containerView 或者 targetView 所属的 observer 进行检查,其他不相关的不会检查,从而避免不必要的消耗。

3、对于 ScrollView,除了上述的触发时机,还需要在滚动的时候进行检查。当给 containerView 指定 options 时,ZHIntersectionObserver 判断如果当前是 UIScrollView,则会自动给 ScrollView 绑定一个监听 contentOffset 的 KVO,当列表滚动的时候就会触发曝光检查,为了避免频繁触发检查,给 KVO 加了截流函数限制,提高性能。

4、除了上面的两种时机,还有两种:一种是切换界面(例如 push pop present dismiss 或者切 tab 等等,这种不会出发上面属性 KVO 的变化,但是 view.window 会变化,所以通过 hook didMoveToWindow 方法来实现触发时机);另外一种是 APP 切换前台后台(ZHIntersectionObserver 内部通过监听 APP 生命周期的 notification 实现);

5、如何实现限制曝光时长?如果要说一个 view 怎样才算曝光,那么是要求这个 view 需要在屏幕内停留一定的时间,而不是快速的滚过屏幕也算,这种属于无效数据,但是这种在平时的业务中实现起来比较繁琐。ZHIntersectionObserver 提供一个 intersectionDuration 参数控制 view 需要在屏幕内曝光多长时间才算曝光。实现的原理是,当 view 第一次曝光的时候,不会马上调用 callback,而是 delay 一个时长(业务设置),经过这个 delay 之后重新把之前需要调用 callback 的 entries 拿出来,重新检查当前 entry 是否还是维持跟 delay 前一样的状态,如果是则调用 callback,否则废弃 entry。

6、如何解决 view 复用的问题?所谓复用问题就是 view 没变,但是 view 承载的数据变了,那我们需要曝光的是数据而不是这个 view,这个 view 只是给我们判断当前是否 visible 而已。解决这个问题的关键是给 targetView 的 options 加一个 dataKey,dataKey 对于每一份不同的数据都是唯一值的,当更新了 view 的数据,需要 update 一下 options 的 dataKey,ZHIntersectionObserver 会自动触发曝光检查,检查会判断是否 dataKey 变化了来决定需不需要重新检查曝光和调用 callback。

其他使用场景

Intersection Observer 除了用在曝光打点,还可以用在其他场景例如:

图片懒加载

无限滚动,甚至实现 view 的复用

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352

推荐阅读更多精彩内容