Event Handing guide for iOS


概述:
每当用户操作iOS设备,iOS系统就会解析用户的操作行为,并将这些操作系统封装成事件发送给相应的App,这些事件源包括以下四种:
  • Touch Event
  • Motion Event
  • Remote Control Event
  • Presses Event


目录:



UIKit Makes it Easy for Your App to Detect Gestures:

原本手指在屏幕的触控事件全都是通过UIView的范畴(非正式协议)事件来监听:
- ( void )touchesBegan:( NSSet < UITouch *> *)touches withEvent:( UIEvent *)event
- ( void )touchesEnded:( NSSet < UITouch *> *)touches withEvent:( UIEvent *)event
- ( void )touchesMoved:( NSSet < UITouch *> *)touches withEvent:( UIEvent *)event
- ( void )touchesCancelled:( NSSet < UITouch *> *)touches withEvent:( UIEvent *)event
识别成功会调用END,识别失败调用cancel,如在view上还有手势识别器的情况下,当手势识别器识别成功,touch非正式协议就会调用cacenl。

但UIKIT直接将再进行封装成各种UIGestureRecognizer用于识别一些通用的手势,甚至直接将手势应用于UIControl的子类,如UIButton、UISlider等,让其能直接相应与自身图形相关的触摸事件。这些手势识别器都是利用target-Action实现的,均将手势与事件直接一一对应。当然我们也可以自己定义自己所需的手势。

Target-Action机制与响应链机制的关系:
Target-Action,其实就是send action message to target when action happen,所以就是一种将事件与响应对象一一绑定的机制。而手势识别器就是应用了该机制,所以在初始化的时候会要求参入target与SEL两个关键参数以实现绑定,达到若某事件发生,就可以调用target-SEL实现效果。
而响应链机制就是iOS中事件传递的机制,它与Target-Action的关系是,当Target-Action中的SEL为nil,也就是对象无法处理事件时,事件就会沿着响应者链条回传。
Built-in Gesture Recognizers common Gestures:

UIKit定义的通用手势识别器:
  • UITapGestureRecognizer(Tapping,any numbers of taps,点击)
  • UIPinchGestureRecognizer(Pinching in and out,for zooming a view,捏)
  • UIPanGestureRecognizer(panning or dragging,拖动)
  • UISwipeGestureRecognizer:(swiping,in any direction,滑动)
  • UIRotationGestureRecognizer(rotating,fingers moving in opposite directions,旋转)
  • UILongPressGestureRecognizer(Long press,also known as “touch and hold”,长按)

Discrete and Continuous Gesture:

手势也分有离散手势与连续手势两种,离散手势如Tap Gesture,只会发送一次Action message。但连续手续,如Pinch Gesture就不一样,它是在手势缩放的整个过程都会连续发送action message。
Responding to Events with Gesture Recognizer:

添加一个手势识别器需要做的三件事:
  1. 创建手势识别器,并绑定Target、Action,或者指定特殊的参数,如numberOfTapsRequired
  2. 将手势识别器添加到View中
  3. 实现手势识别器的Action

响应离散手势与连续手势的区别:
  • 离散手势:只处理一个Action message事件。
  • 连续手势:需要连续处理多个Action message事件。而且在连续手势中还得判断以下几个状态,针对不同的状态进行不同的处理:
    • UIGestureRecognizerStatePossible
    • UIGestureRecognizerStateBegan
    • UIGestureRecognizerStateChanged
    • UIGestureRecognizerStateEnded
    • UIGestureRecognizerStateFailed

所以手势识别的过程实际上就是一个状态机在不同的状态下切换的过程,而且每一次状态的切换都伴随着一个action send to target的过程,如下图所示:


PS:其中Recognized状态已经被End状态取代。
Defining How Gesture Recognizers Interact:

UIView通过一个NSArray属性gestureRecognizers来管理手势识别器,所以一个View也可以同时具有数个手势识别器,可以通过addGestureRecognizer与removeGestureRecognizer来进行添加与移除。

在默认情况下,当一个UIView同时包含若干去手势识别器,这些识别器收到touch的顺序每次都是随机的,但我们可以通过重写或者UIGestureRecognizerDelegate来实现以下的效果:
  1. 制定一个手势识别必须要先执行与另一个手势之前
  2. 允许同时手势进行分析执行
  3. 阻止一个手势识别器去分析touch
通过重写UIGestureRecognizer的子类方法或者实现delegate可以实现以上效果,而且在某些特殊场合十分重要。
如一个界面添加了 swipes and pans手势识别器,这两个手势有比较多的共同点,默认模式容易识别错,所以需要特殊处理。

UIKit中UIControl的子类,不少都是已经封装好特定的手势识别器的了,为了不造成重叠,UIKIT默认若开发者继续添加了相同的手势识别器的,会旁路掉默认的手势识别器。包括以下场景:
  1. 添加Tap到UIButton、UISwitch、UIStepper、UISegmentedControl、UIpageControl
  2. 添加swipe到UISlider
  3. 添加Pan到UISwitch
Gesture Recognizer Interpret Raw Touch Events


手势识别器的原理本质上就是通过UIView通过范畴实现的非正式协议方法来实现的,如下图:

通过重写以上方法即可监听用户在View的触摸行为,其中有两个很重要的参数,UITouch与UIEvent,当iOS在有交互时间产生,UIEvent就会在响应链中传递,
而UIEvent有有两个重要枚举 UIEventType、 UIEventSubtype。

其中 UIEventType的结构如下:
typedef NS_ENUM (NSInteger, UIEventType) {
    UIEventTypeTouches,
    UIEventTypeMotion,
    UIEventTypeRemoteControl,
    UIEventTypePresses
NS_ENUM_AVAILABLE_IOS ( 9 _0),
};

UIEventSubtype的结构如下:
typedef NS_ENUM (NSInteger, UIEventSubtype) {
   
// available in iPhone OS 3.0
    UIEventSubtypeNone                              =
0 ,
   
   
// for UIEventTypeMotion, available in iPhone OS 3.0
    UIEventSubtypeMotionShake                       =
1 ,
   
   
// for UIEventTypeRemoteControl, available in iOS 4.0
    UIEventSubtypeRemoteControlPlay                 =
100 ,
    UIEventSubtypeRemoteControlPause                =
101 ,
    UIEventSubtypeRemoteControlStop                 =
102 ,
    UIEventSubtypeRemoteControlTogglePlayPause      =
103 ,
    UIEventSubtypeRemoteControlNextTrack            =
104 ,
    UIEventSubtypeRemoteControlPreviousTrack        =
105 ,
    UIEventSubtypeRemoteControlBeginSeekingBackward =
106 ,
    UIEventSubtypeRemoteControlEndSeekingBackward   =
107 ,
    UIEventSubtypeRemoteControlBeginSeekingForward  =
108 ,
    UIEventSubtypeRemoteControlEndSeekingForward    =
109 ,
};

从上面的数据结构可以,其实UITouch是封装在UIEvent中的,但这里非正式协议中的方法为了开发者方便把它单独抽离出来成为一个参数。
Regulating the Delivery of Touch to Views



默认的touch event时间传递的顺序如上图所示,很明显每个action是先经过Gesture recognize再到View的。若Gesture recognize先于View完成手势识别,那么view中的touch event分析就会进入cancel状态。

可以通过设置UIGestureRecognizer的delaysTouchesBegin、delaysTouchEnded属性来改变默认的事件传递规则,如下:
  • delaysTouchesBegin(default:NO):禁止信息传递到view
  • delaysTouchEnded(default:YES):手势识别器延迟结束,等待touch非正式协议完成识别。

手势识别器可以通过调用   ignoreTouch:forEvent:来忽略特殊的事件。
Creating a Custom Gesture Recognizer

可以通过继承UIGestureRecognizer并重写以下基础方法,即可实现自定义的手势识别器:
- (void)reset;
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;





当用户因操作而产生事件时,UIKIT就会创建一个UIEvent包含处理事件所需的信息,然后系统把这个事件放置到当前激活的App的时间队列里面。然后处于激活状态的UIApplication对象从任务队列中获取UIEvent并然后将其传递到key window,然后通过响应链找到能处理该事件的对象。

对于不同的UIEvent的传递方式略有不同:
  • Touch event:通过hit-test机制,找到hit-testView,若hit-testView无法处理事件,则沿着响应者链条找寻能处理事件的对象。
  • Motion and remote control event:window对象直接把事件传递给第一响应者。
Hit-Testing return the view where a touch occurred:

hit-test机制寻找hit-test view的例子:


- ( UIView *)hitTest:( CGPoint )point withEvent:( UIEvent *)event
hit-test机制的核心方法的默认实现如下所示:
  1. 调用 pointInside:withEvent:判断屏幕点击的点是否在本view范围内,是返回YES,不是返回NO
  2. 若返回YES,则递归subviews,并调用其hit-test方法,若返回NO,则返回nil
  3. 最终有效返回的View就是hit-test view

The responder chain is made up of responder objects

所有继承于UIResponer的对象都可以成为响应者,均具备响应处理事件的能力,而这些对象组成的链条,称为响应者链条。
响应者链条由第一响应者开始,到UIApplication结束。

第一响应者就是首先接收事件的对象,对象可以通过以下方法主动成为第一响应者:
  • 重写canBecomeFirstResponder方法,并返回YES
  • 调用becomFirstResponder方法。


Event不是唯一依赖响应者链条的,响应者链条常用于以下场景:
  • Touch Events:
  • Motion Event:
  • Remote Control event
  • action message
  • editing-meun message
  • text editing

响应者链条的结构:
首先每个事件都会有自己对应的响应者链条,所以响应者链条是动态生成的。
对应Touch Event:由hit-test机制生成。
对于Motion、Remote Event:由程序设定,设定谁了first responder,然后若first responder无法响应,然后就是按照响应者链条回传,就是first responder——》。。。——》application的过程,中间的传递规律如下图。
The responder chain follows a specific Delivery

响应者链条是专门用于传递用户与设备交互而产生的事件,而别的系统事件是通过的别的方式来传递的,这里得区分。
响应链的传递规则图:






Creating a SubClass of UIResponder
implementing the Touch-event Handing method in your subclass
Tracking the phase and location of a touch event
Retrieving and Querying Touch Objects
Handling Tap Gestures
Handling Swipe and Drag Gestures
Handling a Complex Multitouch Sequence
Specifying Custom Touch Event Behavior
Intercepting Touches by Overriding Hit-Testing
Forwarding Touch Events
Best Practices for Handling Multitouch Events





当用设备移动、摇晃、倾斜,就会生成motion events事件。这类型的事件大多产生于iOS的硬件传感器,如:
  • 加速计:测量设备各个方向上的加速度
  • 陀螺仪:测量设备各个方向上的旋转速率
  • GPS:测量位置

Getting the Current Device Orientation with UIDevice

若想获取设备的方向,可以直接通过UIDevice实现。实例代码如下:
-(void) viewDidLoad {

    // Request to turn on accelerometer and begin receiving accelerometer events

    [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(orientationChanged:) name:UIDeviceOrientationDidChangeNotification object:nil];

}



- (void)orientationChanged:(NSNotification *)notification {

    // Respond to changes in device orientation

}



-(void) viewDidDisappear {

    // Request to stop receiving accelerometer events and turn off accelerometer

    [[NSNotificationCenter defaultCenter] removeObserver:self];

    [[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications];

}
PS:就是通过通知的方式获取设备的方向。
Detecting Shake-Motion Events with UIEvent

由于motion都是直接发给第一响应者,若第一响应者无法处理,则继续在响应者链条回传。

若设置了第一响应者,还得实现以下方法才能完成得到相应的数据,实现功能。
- ( void )motionBegan:( UIEventSubtype )motion withEvent:( UIEvent *)event
- ( void )motionEnded:( UIEventSubtype )motion withEvent:( UIEvent *)event
Setting and Checking Required Hardware Capabilities for Motion Events
略。
Capturing Device Movement with Core Motion

捕抓motion的核心数据的教程。






能实现用外部设备控制多媒体APP,又或者在锁屏的情况下还能控制多媒体。使用命令模式实现。
Preparing Your App for Remote Control Events

能接收远程控制时间,得满足以下条件:
  1. 为各种Action注册handle,MPRemoteCommandCenter 能实现各种事件的注册。
  2. app要处于正在播放的状态。
Handling Remote Control Events

为特定事件添加handle的代码:
MPRemoteCommandCenter *commandCenter = [MPRemoteCommandCenter sharedCommandCenter];
[commandCenter.playCommand addTargetUsingBlock:^(MPRemoteCommandEvent *event) {
    // Begin playing the current track.
    [[MyPlayer sharedPlayer] play];
}
Providing Now Playing Information

通过 MPNowPlayingInfoCenter  对象获取播放信息。
Testing Remote Control Events on a Device








在充满仪式感的生活里,一款能传递心意的小工具总能带来意外惊喜。这款基于Java开发的满屏飘字弹幕工具,正是为热爱生活、乐于分享的你而来——它以简洁优雅的视觉效果,将治愈系文字化作灵动弹幕,在屏幕上缓缓流淌,既可以作为送给心仪之人的浪漫彩蛋,也能成为日常自娱自乐、舒缓心情的小确幸。 作为程序员献给crush的心意之作,工具的设计藏满了细节巧思。开发者基于Swing框架构建图形界面,实现了无边框全屏显示效果,搭配毛玻璃质感的弹幕窗口与圆润边角设计,让文字呈现既柔和又不突兀。弹幕内容精选了30条治愈系文案,从“秋天的风很温柔”到“你值得所有温柔”,涵盖生活感悟、自我关怀、浪漫告白等多个维度,每一条都能传递温暖力量;同时支持自定义修改文案库,你可以替换成专属情话、纪念文字或趣味梗,让弹幕更具个性化。 在视觉体验上,工具采用柔和色调生成算法,每一条弹幕都拥有独特的清新配色,搭配半透明渐变效果与平滑的移动动画,既不会遮挡屏幕内容,又能营造出灵动治愈的氛围。开发者还优化了弹幕的生成逻辑,支持自定义窗口大小、移动速度、生成间隔等参数,最多可同时显示60条弹幕,且不会造成电脑卡顿;按下任意按键即可快速关闭程序,操作便捷无负担。 对于Java学习者而言,这款工具更是一份优质的实战参考。源码完整展示了Swing图形界面开发、定时器调度、动画绘制、颜色算法等核心技术,注释清晰、结构简洁,哪怕是初学者也能轻松理解。开发者在AI辅助的基础上,反复调试优化细节,解决了透明度控制、弹幕碰撞、资源占用等多个问题,这份“踩坑实录”也为同类项目开发提供了宝贵经验。 无论是想给喜欢的人制造浪漫惊喜,用满屏文字传递心意;还是想在工作间隙用治愈文案舒缓压力,或是作为Java学习的实战案例参考,这款满屏飘字弹幕工具都能满足你的需求。它没有复杂的操作流程,无需额外配置环境,下载即可运行,用最纯粹的设计传递最真挚的
<think>嗯……用户这次的问题有点微妙啊。ta似乎在询问编程中“retain”的用法或含义,但上下文很模糊。注意到ta特意提到之前的引用是关于JavaScript中for-in循环的警告,但明确说那不是真实需求,这给我的工作带来了挑战。用户可能遇到的具体场景或许是:1)看到代码中有retain关键字不确定作用2)内存管理时遇到retain/release概念3)函数式编程中接触到了retain方法。考虑到ta引用了JS相关的内容,更可能是前两种情况。我决定采用覆盖面更广的解释策略。先区分动词和名词用法:核心编程含义是“保留内存引用”,这在Objective-C的MRC里特别重要,但现代开发更常见的是在集合操作里作为方法名使用。需要特别强调ARC环境下的变化,避免用户混淆。用户身份推测:可能是初级或中级开发者,遇到不熟悉的API或术语。考虑到ta能准确引用之前的资料,应该有一定学习能力。情绪似乎有些困惑但耐心,愿意提供更多上下文线索。重点应该放在实用场景:比如Swift的数组retain方法实际应用,或者内存管理中的典型错误案例。上次回答快速排序时提到的递归内存问题其实也可以和retain概念呼应——不过这次用户明显更关注术语本身。最后设计的问题要能引导用户澄清需求:是内存管理?API使用?还是算法优化?特意加入函数式编程的选项是因为用户上次接受了递归相关的回答,可能对高阶函数接受度更高。</think> 在编程语境中,`retain` 主要有以下核心含义和用法: --- ### **1. 内存管理 (Memory Management)** - **Objective-C/Swift (手动引用计数 MRC)** 用于增加对象的引用计数,防止对象被释放: ```objectivec [object retain]; // 增加引用计数 ``` 需配对使用 `release` 避免内存泄漏: ```objectivec [object release]; // 减少引用计数 ``` > *现代开发中已被 ARC (自动引用计数) 替代* - **所有权声明 (Ownership)** 在 C/C++ 的智能指针或 Rust 中,`retain` 语义体现在所有权机制上: ```rust let s1 = String::from("Hello"); let s2 = s1; // s1 的所有权转移至 s2,s1 失效 ``` --- ### **2. 集合操作 (Collections)** - **保留特定元素** 如 Swift 的 `retainAll` (类似 `filter`): ```swift var numbers = [1, 2, 3, 4, 5] numbers.retain { $0 % 2 == 0 } // 只保留偶数 → [2, 4] ``` - **Java 的 `LinkedHashMap.retainAll()`** 保留与其他集合的交集: ```java Set<String> set = new HashSet<>(Arrays.asList("A", "B", "C")); set.retainAll(Arrays.asList("B", "D")); // set 变为 ["B"] ``` --- ### **3. 数据持久化 (Data Persistence)** - **缓存/状态保留** 如网页开发中的 `localStorage`: ```javascript localStorage.setItem("theme", "dark"); // 保留用户设置 ``` - **Session 保持** 后端框架中保留用户登录状态: ```python flask.session["user_id"] = 123 # 保留会话数据 ``` --- ### **4. 函数式编程 (Functional Programming)** - **闭包捕获变量 (Closures)** 函数保留其定义时环境的引用: ```javascript function createCounter() { let count = 0; return function() { count += 1; // 闭包保留对 count 的引用 return count; }; } const counter = createCounter(); counter(); // 1 (count 被保留) ``` --- ### **典型错误场景** ```javascript // 错误示例:循环引用导致内存无法释放 class Node { constructor() { this.parent = null; } } let a = new Node(); let b = new Node(); a.child = b; b.parent = a; // 互相 retain → 内存泄漏 ``` --- ### **最佳实践建议** 1. **明确生命周期** 手动管理内存时严格配对 `retain/release` 2. **优先使用不可变数据** 减少意外修改保留的数据 3. **警惕循环引用** 使用弱引用 (`weakref` 或 `WeakMap`) 解除强引用链 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值