干净优雅的做iOS应用内全局交互屏蔽

033247d3a1907dae8a935b306b5f190d.jpeg

3cb024ac812a64b224d8dbdcf519850b.gif

本文字数:4930

预计阅读时间:28分钟

1ac07b8dee034ee6a514ebbcb9e2940d.png

01

交互屏蔽的需求

很多应用开发者都会遇到这样一个需求,当程序需要处理某个敏感的核心任务,或者执行某些动画时,需要杜绝一切外部干扰,优先保证任务的完成,之后再去处理其它任务。否则如果在处理过程中受到外部事件的干扰,可能会引入严重的问题,而规避这些问题需要额外编写过多的逻辑。

例如,当程序在忙着清理应用内缓存的过程中去处理其它任务,这时候由于其它任务可能会产生新的缓存,这就会和现有的任务冲突。所以在清理缓存的过程中,app 一般会暂时中断用户和非用户的请求,优先保证缓存清理的完成。

所以,为了简化产品设计逻辑,开发者一般会选择在处理任务时暂时屏蔽其它任务,优先保障现有任务的完成。

举例来说,当用户点击清理缓存时,应用程序可能会弹出一个带有进度条的清理界面,在该界面下,清理工作紧张的进行着,并且告知用户正在清理任务,请稍候。

另一个需求是和动画有关,有时候我们在应用内可能会执行一些小动画,例如按钮的淡入淡出,整个页面的切换等。这些动画可能不会因为用户做快速的操作导致程序崩溃,但是因为每个动画都要时间完成,如果用户快速乱点的话,有可能会出现意想不到的情况。

例如,假设用户点击某个开关切换按钮,开关状态为开时,屏幕侧边以动画形式弹出侧边栏,当开关状态为关时,屏幕侧边栏以动画形式消失。那么如果用户快速反复点击按钮,而开发者没有处理好开关切换间隙的逻辑的话,那么就会出现侧边栏弹出动画还没执行完,就立刻消失的情况。

值得注意的是,这一类事件包括但不限于用户触摸事件,还有屏幕重力感应的变化等非用户输入事件,这就意味着这一类问题如果要优雅解决的话,不能单靠添加一个"触摸屏蔽层"。

02

常见的解决办法

对于以上问题,开发者选择的解决办法主要是两种:

第一个办法,设计一个布尔变量记录当前是否正在执行任务(或处理动画),处理这个过程中的交互逻辑。

这个办法本身没什么问题,但是开发者不得不针对每个任务去编写对应的逻辑,这样写起来就特别容易散乱。

第二个办法,设置 UIView 或者控件的 userInteractionEnabled false,并在合适的时机重新变为 true

这样做有个好处是,将整个 UIView 设置不可交互后,用户点击其它按钮也不会造成影响,但同时,如果对每个 UIView 去处理类似的逻辑,一不小心很容易出现 bug,最后导致整个 UIView 都卡住无法点击。

另外,应用可能存在多个 UIView,你锁住了一个 UIView,其它 UIView 的点击情况是否要考虑呢?

有些开发者用了更好的办法,他们直接用 UIViewControllerUIView 来做 userInteractionEnabled 的处理,这样的解决方案更进步了,但是同样存在多个 UIViewController 这个问题,虽然有效,但还欠缺优雅。

另一方面,如上述的所有方法,仅仅能拦截"触摸事件",而不能拦截非触摸事件,例如加速器,摄像头事件等,如果代码针对这些事件会做出响应,而开发者不希望在任务期间去响应他们,将被迫去添加逻辑来屏蔽才行。

03

重新理解 UIApplication

我们对 UIApplication 不陌生了,我们经常需要通过调用UIApplication.sharedApplication

在 iOS 的应用层 API 中,UIKit 最顶层的交互机制是通过 UIApplication 的 方法下发的 sendEvent

- (void)sendEvent: (UIEvent*)event

UIEvent 不止包括触摸事件,它还支持例如加速度事件等别的事件类型。

// UIEvent 的事件类型
typedef NS_ENUM(NSInteger, UIEventType) {UIEventTypeTouches,UIEventTypeMotion,UIEventTypeRemoteControl,UIEventTypePresses,UIEventTypeScroll,UIEventTypeHover,UIEventTypeTransform,
};

以上是 UIApplication 中的事件类型,其中最值得关注的是 UIEventTypeTouches 和 UIEventTypeMotion,因为这是开发者最常用于响应输入的事件。

04

如何拦截 sendEvent,先搞懂 UIApplicationMain

要拦截 sendEvent,就要了解 UIApplicationMain,几乎每个 iOS 开发者都会碰到它,因为它就在 main 函数里:

int main(int argc, char * argv[]) {NSString* appDelegateClassName;@autoreleasepool {appDelegateClassName = NSStringFromClass([AppDelegate class]);}return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

UIApplicationMain 相当于 iOS 应用自己的 main 函数,它的参数有 4 个,分别为 argcargvprincipalClassNamedelegateClassName

其中前两个参数就是 C 语言 main 函数的参数,appDelegateClassName 传入的是 AppDelegate 的类名,第三个参数则传入用户自定义的 UIApplication 子类。

我们定义一个继承自 UIApplicationMyApplication 类后,main 函数就可以传入 MyApplication 类了。

int main(int argc, char * argv[]) {NSString* appDelegateClassName;NSString* applicationClassName;@autoreleasepool {appDelegateClassName = NSStringFromClass([AppDelegate class]);applicationClassName = NSStringFromClass([MyApplication class]);}return UIApplicationMain(argc, argv, applicationClassName, appDelegateClassName);
}

还有一个方法可以不通过修改 main 函数来指定 MyApplication,在 XCodeInfo.plist 中,新建字符串键 “Principal class”,其值填入子类名,即本例的 MyApplication,那么当 main 函数传入的参数是 nil 时,Info.plist 所注册的 “Principal class” 将会作为指定类。如果全部没有指定,则默认为 UIApplication

05

sendEvent 拦截实现

MyApplication 类的实现部分,我们可以开始继承 sendEvent :

- (void)sendEvent: (UIEvent*)event {[super sendEvent: event];
}

当应用产生了 UIEvent 事件时,系统会调用 sendEvent,此时因为我们注册了 MyApplication,所以调用的是我们定义的 sendEvent 方法。在上个例子里,sendEvent 直接调用了父类的 sendEvent,相当于对所有事件都采取了默认处理。

接下来,如果我们打算屏蔽所有的加速器事件,那么可以这么写:

- (void)sendEvent: (UIEvent*)event {if (event.type == UIEventTypeMotion) {NSLog(@"UIEventTypeMotion");return;}[super sendEvent: event];
}

这样,如果应用内有处理摇一摇的功能,以上方法可以保证摇一摇事件不会下发。

06

交互屏蔽的接口设计

显然,我们需要更弹性的处理 sendEvent,直接屏蔽的办法是“一刀切”,更恰当的做法是需要屏蔽的时候才让它屏蔽。因此,我们可以为 MyApplication 设计一个布尔属性 eventdisabled:

@interface MyApplication()
@property (nonatomic) BOOL eventDisabled;
@end

该属性默认值是 false,即允许所有 event ,这样,sendEvent 方法的实现更改如下:

- (void)sendEvent: (UIEvent*)event {if (self.eventDisabled && (event.type == UIEventTypeTouches || event.type == UIEventTypeMotion)) {return;}[super sendEvent: event];
}

当 eventdisabled 为真时,app 在全局范围内禁用了用户的触摸和加速器输入事件,只有当 eventdisabled 为假时,一切照常进行。

虽然现在可以通过变量让 app 实现交互的禁用和启用了,但是我们可以设计的更弹性一点,做一个延时机制,保证延时过后交互可以恢复正常,不然开发者四处写布尔值设置的代码,一旦稍有不慎,整个界面卡住就糟了,为了程序的健壮性,可以设计如下方法:

- (void)disableUserInteraction: (NSTimeInterval)duration {self.userInteractionEnabled = NO;dispatch_after(dispatch_time(DISPATCH_TIME_NOW, duration*NSEC_PER_SEC), dispatch_get_main_queue(), ^{self.userInteractionEnabled = YES;});
}

然后,我们把该方法放到 UIApplication 的扩展中,在 MyApplication.h 加入:

@interface UIApplication()
- (void)disableUserInteraction: (NSTimeInterval)duration;
@end

这样,当我们需要临时禁用用户输入时,可以这么调用:

[UIApplication.sharedApplication disableUserInteraction: 0.3];

此时应用会在 0.3 秒内处于禁用状态,并在稍后自动恢复。

07

如何进一步优化

以上介绍的屏蔽方案已经可以解决大部分日常需求,但是还有优化空间。

例如,当有多次调用 disableUserInteraction 时,例如:

[UIApplication.sharedApplication disableUserInteraction: 0.3];
[UIApplication.sharedApplication disableUserInteraction: 1.3];

我们会发现,0.3 秒后应用就恢复了交互,而 1.3 秒的屏蔽"失效"了。

为了解决这个问题,可以引入一个计数器,保证让交互屏蔽满足最长时间的那个请求。这部分大家可以作为习题自行解决。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/135373.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux日志管理-logrotate(crontab定时任务、Ceph日志转储)

文章目录 一、logrotate概述二、logrotate基本用法三、logrotate运行机制logrotate参数 四、logrotate是怎么做到滚动日志时不影响程序正常的日志输出呢?Linux文件操作机制方案一方案二 五、logrotate实战--Ceph日志转储参考 一、logrotate概述 logrotate是一个用于…

Failed to connect to bitbucket.org port 443 错误原因, 解决办法

最近使用SourceTree来访问bitbucket.org的代码托管Git, 当Pull或者Push发现操作失败: Failed to connect to bitbucket.org port 443 错误原因: 无法链接到网站地址, 可能是DNS解析IP地址错误, 或者网站维护, 大概率是被墙或者DNS解析错误. 解决办法: 如果您的浏览器能够访问b…

无所不知的Mac效率神器 Alfred 4,一顶神器的小帽子!

肯定很多人在工作、学习或者写东西的时候,总想着怎样可以提高效率,节约时间。现在每天用得最多的电脑,怎样才能够让工作效率更高呢?带着这样的一个目的,使用了很多的小工具,有些可以简化工作,有…

U8用友ERP本地部署异地远程访问:内网端口映射外网方案

文章目录 前言1. 服务器本机安装U8并调试设置2. 用友U8借助cpolar实现企业远程办公2.1 在被控端电脑上,点击开始菜单栏,打开设置——系统2.2 找到远程桌面2.3 启用远程桌面 3. 安装cpolar内网穿透3.1 注册cpolar账号3.2 下载cpolar客户端 4. 获取远程桌面…

2.6W字系统总结,带你实现 Linux 自由!

以前就聊过大多数应届生入职后会遇到的两大拦路虎分别是Git和Linux,其中关于Git,前段时间已经分享过自己工作两年多以来用过的比较实用的一些命令了,这些命令可能不是最花哨的,但绝对是最实用的。 今天分享一份很全面的Linux常用…

005-第一代光电小工具(一)

第一代光电小工具(一) 文章目录 第一代光电小工具(一)项目介绍大致原理描述核心控件QCustomPlot关于QCustomPlot 播放音频软件截图 关键字: Qt、 Qml、 QCustomPlot、 曲线、 SQLite 项目介绍 欢迎来到我们的 QML & C 项目!这个项目结合了 QML&…

如何看待近期tiktok大批量封号问题?

近期大量客户反馈Tiktok账号大批量被封,了解这类客户的情况后,得知被封的账号大部分都是为搬运国内视频上传的账号类型,从这次的事件可以看出,tk对视频的检测以及内容要求度越来越高,Tk风控升级,回导致很多…

黑马JVM总结(十一)

(1)垃圾回收概述 前面我们学了堆,里面有一个垃圾回收的机制 (2)判断垃圾_引用计数 指只要有一个对象被其他变量所引用,我们就让这个对象的计数加1,有个一变量不在引用,让它的计数…

3D动画制作和渲染需要什么样的硬件规格?

动画是艺术与技术的令人兴奋的融合,为无限的创造力提供了广阔的画布。为了将创意愿景变为现实,动画师需要适合其工艺的强大计算资源。每个动画项目都有不同的硬件需求,无论是制作简单的 2D 动画还是构建复杂的 3D 世界。因此,有抱…

网络编程——基础知识

全文目录 网络发展协议OSI七层模型TCP/IP五层(或四层)模型 网络传输网络地址IP地址MAC地址 网络通信的本质 网络发展 网络没有出来之前计算机都是相互独立的: 网络就是将独立的计算机连接在一起,局域网和广域网的区别只是范围上的大小: 局域…

华为云云服务器云耀L实例评测 | 从零开始:华为云云服务器L实例使用教程

🌷🍁 博主猫头虎(🐅🐾)带您 Go to New World✨🍁 🦄 博客首页——🐅🐾猫头虎的博客🎐 🐳 《面试题大全专栏》 🦕 文章图文…

打造“共富果园” 广东乳源推动茶油全产业链高质量发展

新华网广州9月13日电(李庆招)金秋九月,瓜果飘香,油茶也将迎来采摘期。13日,一场以“中国健康油 茶油新势力”为主题的乳源茶油12221市场体系之产业大会暨供销对接会在广州举行。来自茶油行业的专家、企业家齐聚一堂&am…

高性能数据JS网格 Bryntum Grid 5.5.2 Crack

高性能数据网格 Bryntum Grid 是一个高性能的网络表格组件。它是用纯 JavaScript 构建的,并且可以轻松地与所有主要 JS 框架集成。 功能丰富 Bryntum Grid 具有您期望从专业网格组件获得的所有功能,包括: 很好的表现;很好的绩效 没有人喜欢缓…

Stable Diffusion 系统教程 | 强大的ControlNet 控制网

2023年的2月13日,一款名叫ControlNet的插件横空出世,AI绘画变得更加可控 ControlNet直译过来很简单,就叫做控制网,开发者是一名华裔,毕业于苏州大学,目前在斯坦福做读博士一年级,大佬大佬&…

Vivado XADC IP核 使用详解

本文介绍Vivado中XADC Wizard V3.3的使用方法。 XADC简介 XADC Wizard Basic Interface Options: 一共三种,分别是AXI4Lite、DRP、None。勾选后可在界面左侧看到相应通信接口情况。Startup Channel Selection Simultaneous Selection:同时监…

【C语言】指针的进阶(二)—— 回调函数的讲解以及qsort函数的使用方式

目录 1、函数指针数组 1.1、函数指针数组是什么? 1.2、函数指针数组的用途:转移表 2、扩展:指向函数指针的数组的指针 3、回调函数 3.1、回调函数介绍 3.2、回调函数的案例:qsort函数 3.2.1、回顾冒泡排序 3.2.1、什么是qso…

RFID插件,物联网技术新利器_三叠云

RFID 插件 路径 拓展 >> 插件 功能简介 新增「RFID」插件。RFID 插件使系统对资产管理、人员定位、进出库管理等自动化应用全面适配,极大拓展了三叠云系统的应用场景。 示例: RFID在表单中应用: RFID在移动端应用: 实时…

腾讯轻联:带你创造属于自己的AI小助手

陈老老老板🤴 🧙‍♂️本文专栏:生活(主要讲一下自己生活相关的内容)生活就像海洋,只有意志坚强的人,才能到达彼岸。 🧙‍♂️本文简述:参加腾讯全球数字生态大会,了解到腾讯轻联企业…

Java8中判断一个对象不为空存在一个类对象是哪个

Java8中判断一个对象不为空存在一个类对象是哪个? 在Java 8中,你可以使用java.util.Optional类来处理可能为空的对象。Optional类可以帮助你优雅地处理空值情况,而不需要显式地进行空值检查。 这是一个简单的Optional示例: imp…

java反序列化---URLDNS链

目录 一、前言 二、原理分析 三、代码实现 一、前言 URLDNS链相较于其他java反序列化链是比较简单的,只需要几步调用就能触发 所以学习java反序列化,最好从URLDNS链出发,初步了解如何跟进,以及反射获取类、方法等 使用这条链…