英雄联盟一键屏蔽所有人聊天(英雄联盟一键屏蔽所有人)
干净优雅的做iOS应用内全局交互屏蔽
01
交互屏蔽的需求
很多应用开发者都会遇到这样一个需求,当程序需要处理某个敏感的核心任务,或者执行某些动画时,需要杜绝一切外部干扰,优先保证任务的完成,之后再去处理其它任务。否则如果在处理过程中受到外部事件的干扰,可能会引入严重的问题,而规避这些问题需要额外编写过多的逻辑。
例如,当程序在忙着清理应用内缓存的过程中去处理其它任务,这时候由于其它任务可能会产生新的缓存,这就会和现有的任务冲突。所以在清理缓存的过程中,app 一般会暂时中断用户和非用户的请求,优先保证缓存清理的完成。
所以,为了简化产品设计逻辑,开发者一般会选择在处理任务时暂时屏蔽其它任务,优先保障现有任务的完成。
举例来说,当用户点击清理缓存时,应用程序可能会弹出一个带有进度条的清理界面,在该界面下,清理工作紧张的进行着,并且告知用户正在清理任务,请稍候。
另一个需求是和动画有关,有时候我们在应用内可能会执行一些小动画,例如按钮的淡入淡出,整个页面的切换等。这些动画可能不会因为用户做快速的操作导致程序崩溃,但是因为每个动画都要时间完成,如果用户快速乱点的话,有可能会出现意想不到的情况。
例如,假设用户点击某个开关切换按钮,开关状态为开时,屏幕侧边以动画形式弹出侧边栏,当开关状态为关时,屏幕侧边栏以动画形式消失。那么如果用户快速反复点击按钮,而开发者没有处理好开关切换间隙的逻辑的话,那么就会出现侧边栏弹出动画还没执行完,就立刻消失的情况。
值得注意的是,这一类事件包括但不限于用户触摸事件,还有屏幕重力感应的变化等非用户输入事件,这就意味着这一类问题如果要优雅解决的话,不能单靠添加一个"触摸屏蔽层"。
02
常见的解决办法
对于以上问题,开发者选择的解决办法主要是两种:
第一个办法,设计一个布尔变量记录当前是否正在执行任务(或处理动画),处理这个过程中的交互逻辑。
这个办法本身没什么问题,但是开发者不得不针对每个任务去编写对应的逻辑,这样写起来就特别容易散乱。
第二个办法,设置 UIView 或者控件的 userInteractionEnabled 为 false,并在合适的时机重新变为 true 。
这样做有个好处是,将整个 UIView 设置不可交互后,用户点击其它按钮也不会造成影响,但同时,如果对每个 UIView 去处理类似的逻辑,一不小心很容易出现 bug,最后导致整个 UIView 都卡住无法点击。
另外,应用可能存在多个 UIView,你锁住了一个 UIView,其它 UIView 的点击情况是否要考虑呢?
有些开发者用了更好的办法,他们直接用 UIViewController 的 UIView 来做 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 个,分别为 argc, argv,principalClassName 和 delegateClassName 。
其中前两个参数就是 C 语言 main 函数的参数,appDelegateClassName 传入的是 AppDelegate 的类名,第三个参数则传入用户自定义的 UIApplication 子类。
我们定义一个继承自 UIApplication 的 MyApplication 类后,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,在 XCode 的 Info.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 { selferInteractionEnabled = NO; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, duration*NSEC_PER_SEC), dispatch_get_main_queue(), ^{ selferInteractionEnabled = 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 秒的屏蔽"失效"了。
为了解决这个问题,可以引入一个计数器,保证让交互屏蔽满足最长时间的那个请求。这部分大家可以作为习题自行解决。
作者:姚坚锋
来源:微信公众号:搜狐技术产品
出处:/d/file/gt/2023-09/lf0brqshi3y
声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送至邮件举报,一经查实,本站将立刻删除。转载务必注明出处:http://www.hixs.net/article/20240416/169625651194696.html