02-iOS 多媒体技术| 图形处理框架-UIKit要点回顾1【UITouch、UIEvent、手势处理、UIResponder、UIApplication等】

前言

我们在前面一篇文章中,对在iOS视觉处理相关框架进行了简介。

在本篇文章主要针对 其中 的 UIKit框架的核心要点,进行一个回顾,作为一个复习和总结。

UIKit框架 是iOS应用程序开发的基础框架之一,也是iOS开发中的一个核心框架。它提供了一系列的类和组件,通过UIKit,开发者可以快速构建各种界面元素、实现用户交互动画效果。

现在Apple也推出了 SwiftUI 用于 构建界面,实现用户交互 和 动画。我比较倾向于 在写 灵动岛 适配的时候使用 SwiftUI ,做应用的业务页面的时候,使用 UIKit框架

整篇文章的内容主要包括:

一、UIKit中的核心对象

UIKit框架的职责

我们先引入一段官方对UIKit的介绍

UIKit provides a variety of features for building apps, including components you can use to construct the core infrastructure of your iOS, iPadOS, or tvOS apps. The framework provides the window and view architecture for implementing your UI, the event-handling infrastructure for delivering Multi-Touch and other types of input to your app, and the main run loop for managing interactions between the user, the system, and your app.

UIKit also includes support for animations, documents, drawing and printing, text management and display, search, app extensions, resource management, and getting information about the current device. You can also customize accessibility support, and localize your app's interface for different languages, countries, or cultural regions.

UIKit works seamlessly with the SwiftUI framework, so you can implement parts of your UIKit app in SwiftUI or mix interface elements between the two frameworks. For example, you can place UIKit views and view controllers inside SwiftUI views, and vice versa.

To build a macOS app, you can use SwiftUI to create an app that works across all of Apple's platforms, or use AppKit to create an app for Mac only. Alternatively, you can bring your UIKit iPad app to the Mac with Mac Catalyst.

Important

Use UIKit classes only from your app's main thread or main dispatch queue, unless otherwise indicated in the documentation for those classes. This restriction particularly applies to classes that derive from UIResponder or that involve manipulating your app's user interface in any way.

简扼地概括一下UIKit框架的职责就是:

  • 通过UIKit,开发者可以快速构建各种界面元素、实现用户交互动画效果等。
    • 构建UI界面
      • 用于实现 UI 的窗口和视图架构
    • 交互事件处理
      • 用于向应用程序提供多点触控其他类型输入的事件处理基础设施
      • 用于管理用户系统应用程序之间交互的主运行循环
    • 动画效果等
      • UIKit 还包括对动画、文档、绘图和打印、文本管理和显示、搜索、应用程序扩展、资源管理以及获取有关当前设备的信息的支持。您
  • 只可以通过主线程或者主队列中进行对UIKit中的类的使用(凡是由UIResponder派生的类在没有任何相关说明时都适用)
  • UIKit 可以 与 SwiftUI无缝协作

继承架构图

首先 引入 一张 继承架构图: 我们从这张继承架构图,可以看到,UIKit中的所有类都继承自OC中的基类NSObject(Swift中虽然重写了UIKit的核心类、Foundation的核心类,这一点同样适用)

想进一步了解Cocoa框架中,UIKit框架类继承体系可以参考我的 这篇文章

1. 核心对象和核心要点

UIKit中的 核心要点核心类 我们前面已经 了解了 UIKit框架的 职责UIKit框架 类的继承关系。 现在 我们不难 得出 UIKit框架的核心要点:

  • 交互事件处理的基础架构。相关类:
    • UIEvent
    • UITouch
    • 用户与应用程序的交互
      • UIGestureRecognizer
    • UIResponder
    • 系统与应用程序的交互
      • AppDelegate(遵守了UIApplicationDelegate协议)
    • 其他事件输入:
      • ...
  • 构建UI界面的基础架构。相关类:
    • UIScreen
    • (派生自UIResponder)
      • UIView
        • UIWindow
        • UIControl
          • UIButton
      • UIApplication
      • UIViewController

2. UIKit中常用的UI组件

根据我们多年的项目实施经验,我们可以把UIKit框架中常用的UI组件列举出来,为了更直观地了解类重要程度,我们 按照派生关系的方式列举:

  • UIResponder
    • UIView
      • UIWindow
      • UILabel
      • UIImageView
      • UIScrollView
        • UITableView
        • UICollectionView
        • UITextView
      • UITableViewCell
      • UIStackView
      • UIControl
        • UIButton
        • UITextField
        • UISwitch
        • UISlider
        • UIDatePicker
        • UIPageControl
        • UISegmentControl
      • UIWebView(已经有更优秀的WKWebView代替)
      • WKWebView
      • UITabBar
      • UINavigationBar
      • UIToolBar
      • UIAlertView
      • UIActionSheet
      • UIProgressView
      • UIPickerView
      • UISearchBar
      • UIActivityIndicatorView
    • UIViewController
      • UITabBarController
      • UINavigationController
        • UIImagePickerController
        • UIVideoEditorController
      • UISplitViewController(iPad开发)
      • UISearchController

二、事件交互|UITouch

1. UITouch简介

UITouch 类是用于记录触控事件的对象

  • 每当用户在屏幕上触摸时,系统就会生成一个 UITouch 对象来表示这次触摸事件的相关信息
  • 记录的事件信息包括屏幕上发生的触摸的:位置size尺寸时间阶段移动力度

2. UITouch的创建与销毁

  • 当用户用一根手指触摸屏幕时,会创建一个与手指相关联的UITouch对象,一根手指对应一个UITouch对象。
  • 当手指移动时,系统会更新同一个UITouch对象,使之能够一直保存该手指在的触摸位置。
  • 当手指离开屏幕时,系统会销毁相应的UITouch对象

3. UITouch记录触控事件的常用属性

objc 复制代码
触摸产生时所处的窗口
@property(nonatomic,readonly,retain) UIWindow    *window;
触摸产生时所处的视图
@property(nonatomic,readonly,retain) UIView      *view;
短时间内点按屏幕的次数,可以根据tapCount判断单击、双击或更多的点击
@property(nonatomic,readonly) NSUInteger          tapCount;
记录了触摸事件产生或变化时的时间,单位是秒
@property(nonatomic,readonly) NSTimeInterval      timestamp;
当前触摸事件所处的状态,包括`began`、`moved`、`stationary`、`ended` 和 `cancelled`
@property(nonatomic,readonly) UITouchPhase        phase;

typedef NS_ENUM(NSInteger, UITouchPhase) {
    UITouchPhaseBegan,             // 表示触摸事件刚开始,手指刚接触到屏幕的时刻。whenever a finger touches the surface. 
    UITouchPhaseMoved,             // 表示手指在屏幕上移动时的阶段,即触摸事件的位置发生了变化 whenever a finger moves on the surface.
    UITouchPhaseStationary,        // 表示触摸事件的位置在屏幕上保持不变,手指未发生移动 whenever a finger is touching the surface but hasn't moved since the previous event.
    UITouchPhaseEnded,             // 表示触摸事件结束,手指从屏幕上抬起的时刻whenever a finger leaves the surface.
    UITouchPhaseCancelled,         // 表示触摸事件被取消,通常是因为系统中断了触摸事件的处理,例如突然来电、系统警告等情况 whenever a touch doesn't end but we need to stop tracking (e.g. putting device to face)
    UITouchPhaseRegionEntered   API_AVAILABLE(ios(13.4), tvos(13.4)) API_UNAVAILABLE(watchos),  // whenever a touch is entering the region of a user interface
    UITouchPhaseRegionMoved     API_AVAILABLE(ios(13.4), tvos(13.4)) API_UNAVAILABLE(watchos),  // when a touch is inside the region of a user interface, but hasn't yet made contact or left the region
    UITouchPhaseRegionExited    API_AVAILABLE(ios(13.4), tvos(13.4)) API_UNAVAILABLE(watchos),  // when a touch is exiting the region of a user interface
};

4. UITouch|获取触控位置的常用方法

objc 复制代码
- (CGPoint)locationInView:(UIView *)view;
//返回值表示触摸在view上的位置
//这里返回的位置是针对view的坐标系的(以view的左上角为原点(0, 0))
//调用时传入的view参数为nil的话,返回的是触摸点在UIWindow的位置
- (CGPoint)previousLocationInView:(UIView *)view;
//记录了前一个触摸点的位置

三、事件交互|UIEvent

1. UIEvent简介

UIEvent: 是事件对象,用于记录事件产生的时刻事件类型 每产生一个事件,就会产生一个UIEvent对象

2. UIEvent事件类型介绍

应用程序可以接收许多不同类型的事件,包括触摸事件运动事件远程控制事件按压事件

  • 触摸事件是最常见的,并且被传递到最初发生触摸的视图。
  • 运动事件由 UIKit 触发,并且与 Core Motion 框架报告的运动事件分开。
  • 远程控制事件允许响应者对象接收来自外部配件或耳机的命令,以便它可以管理音频和视频。例如:
    • 播放视频或跳到下一个音轨
    • 按下事件表示与游戏控制器、Apple TV 遥控器或其他具有物理按钮的设备的交互。type您可以使用和属性确定事件的类型subtype

  1. 触摸事件(Touch Events)
    • UIEventSubtype.touches: 表示触摸事件的子类型。它包括以下几种:
      • UIEventSubtype.touchesBegan: 表示触摸事件开始
      • UIEventSubtype.touchesMoved: 表示触摸事件移动
      • UIEventSubtype.touchesEnded: 表示触摸事件结束
      • UIEventSubtype.touchesCancelled: 表示触摸事件被取消
  2. 摇晃事件(Motion Events)
    • UIEventSubtype.motionShake: 表示设备摇晃事件。
  3. 远程控制事件(Remote Control Events)
    • UIEventSubtype.remoteControlPlay: 表示远程控制播放事件。
    • UIEventSubtype.remoteControlPause: 表示远程控制暂停事件。
    • UIEventSubtype.remoteControlStop: 表示远程控制停止事件。
    • UIEventSubtype.remoteControlTogglePlayPause: 表示远程控制切换播放/暂停事件。
    • UIEventSubtype.remoteControlNextTrack: 表示远程控制下一曲事件。
    • UIEventSubtype.remoteControlPreviousTrack: 表示远程控制上一曲事件。
    • UIEventSubtype.remoteControlBeginSeekingBackward: 表示远程控制开始后退事件。
    • UIEventSubtype.remoteControlEndSeekingBackward: 表示远程控制结束后退事件。
    • UIEventSubtype.remoteControlBeginSeekingForward: 表示远程控制开始快进事件。
    • UIEventSubtype.remoteControlEndSeekingForward: 表示远程控制结束快进事件。
  4. 按键事件(Press Events)
    • UIEventSubtype.presses: 表示按键事件的子类型。它包括以下几种:
      • UIEventSubtype.pressesBegan: 表示按键事件开始
      • UIEventSubtype.pressesChanged: 表示按键事件发生变化
      • UIEventSubtype.pressesEnded: 表示按键事件结束
      • UIEventSubtype.pressesCancelled: 表示按键事件被取消

3. UIEvent记录事件信息的常用属性

objc 复制代码
//事件类型
@property(nonatomic,readonly) UIEventType     type;
@property(nonatomic,readonly) UIEventSubtype  subtype;
//事件产生的时间
@property(nonatomic,readonly) NSTimeInterval  timestamp;

4. UIEvent|不同事件类型的回调方法

各类型事件的回调方法都在UIResponder中

4.1 触控事件

objc 复制代码
// Generally, all responders which do custom touch handling should override all four of these methods.
// Your responder will receive either touchesEnded:withEvent: or touchesCancelled:withEvent: for each
// touch it is handling (those touches it received in touchesBegan:withEvent:).
// *** You must handle cancelled touches to ensure correct behavior in your application.  Failure to
// do so is very likely to lead to incorrect behavior or crashes.

//触摸开始
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
//触摸移动
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
//触摸结束
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
//触摸取消
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesEstimatedPropertiesUpdated:(NSSet<UITouch *> *)touches API_AVAILABLE(ios(9.1));

单点触控多点触控

  • 4个触摸事件处理方法中,都有 NSSet<*touches>UIEvent *event 两个参数
  • 一次完整的触摸过程中,只会产生一个事件对象,4个触摸方法都是同一个event参数
  • 如果两根手指同时触摸一个view,那么view只会调用一次touchesBegan:withEvent:方法,touches参数中装着2个UITouch对象
  • 如果这两根手指一前一后分开触摸同一个view,那么view会分别调用2次touchesBegan:withEvent:方法,并且每次调用时的touches参数中只包含一个UITouch对象
  • 所以根据touches中UITouch的个数可以判断出是单点触摸还是多点触摸

4.2 按键事件

objc 复制代码
// Generally, all responders which do custom press handling should override all four of these methods.
// Your responder will receive either pressesEnded:withEvent or pressesCancelled:withEvent: for each
// press it is handling (those presses it received in pressesBegan:withEvent:).
// pressesChanged:withEvent: will be invoked for presses that provide an analog value
// (like thumbsticks or analog push buttons)
// *** You must handle cancelled presses to ensure correct behavior in your application.  Failure to
// do so is very likely to lead to incorrect behavior or crashes.

- (void)pressesBegan:(NSSet<UIPress *> *)presses withEvent:(nullable UIPressesEvent *)event API_AVAILABLE(ios(9.0));
- (void)pressesChanged:(NSSet<UIPress *> *)presses withEvent:(nullable UIPressesEvent *)event API_AVAILABLE(ios(9.0));
- (void)pressesEnded:(NSSet<UIPress *> *)presses withEvent:(nullable UIPressesEvent *)event API_AVAILABLE(ios(9.0));
- (void)pressesCancelled:(NSSet<UIPress *> *)presses withEvent:(nullable UIPressesEvent *)event API_AVAILABLE(ios(9.0));

4.3 摇晃事件

objc 复制代码
- (void)motionBegan:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event API_AVAILABLE(ios(3.0));
- (void)motionEnded:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event API_AVAILABLE(ios(3.0));
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event API_AVAILABLE(ios(3.0));

4.4 远程控制事件

objc 复制代码
- (void)remoteControlReceivedWithEvent:(nullable UIEvent *)event API_AVAILABLE(ios(4.0));

四、事件交互|UIGestureRecognizer

1. UIGestureRecognier简介

  • iOS 3.2之后,苹果推出了手势识别功能(Gesture Recognizer),在触摸事件处理方面,大大简化了开发者的开发难度。
  • 利用UIGestureRecognizer,能轻松识别用户在某个view上面做的一些常见手势。
  • UIGestureRecognizer是一个抽象类,对iOS中的事件传递机制面向应用进行封装,将手势消息的传递抽象为了对象。
  • 其中定义了所有手势的基本行为,使用它的子类才能处理具体的手势。

2. 手势的抽象类------UIGestureRecognizer

UIGestureRecognizer将一些和手势操作相关的方法抽象了出来,但它本身并不实现什么手势,因此,在开发中,我们一般不会直接使用UIGestureRecognizer的对象,而是通过其子类进行实例化,iOS系统给我们提供了许多用于实例的子类,这些我们后面再说,我们先来看一下,UIGestureRecognizer中抽象出了哪些方法。

2.1 初始化方法

UIGestureRecognizer类为其子类准备好了一个统一的初始化方法,无论什么样的手势动作,其执行的结果都是一样的:

  • 初始化

    触发一个方法,可以使用下面的方法进行统一的初始化:

    objc 复制代码
    - (instancetype)initWithTarget:(nullable id)target action:(nullable SEL)action
  • 当然,如果我们使用alloc-init的方式,也是可以的,下面的方法可以为手势添加触发的selector:

    objc 复制代码
    - (void)addTarget:(id)target action:(SEL)action;
  • 手势移除

    与之相对应的,我们也可以将一个selector从其手势对象上移除:

    objc 复制代码
    - (void)removeTarget:(nullable id)target action:(nullable SEL)action;
  • 一个手势,多个触发方法

    因为addTarget方式的存在,iOS系统允许一个手势对象可以添加多个selector触发方法,并且触发的时候,所有添加的selector都会被执行,我们以点击手势示例如下:

    objc 复制代码
    - (void)viewDidLoad {
        [super viewDidLoad];
        UITapGestureRecognizer *tap1 = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tap1:)];
        [tap1 addTarget:self action:@selector(tap2:)];
        [self.view addGestureRecognizer:tap1];
    }
    -(void)tap1:(UITapGestureRecognizer *)tap
    {
        NSLog(@"%s",__func__);
    }
    -(void)tap2:(UITapGestureRecognizer *)tap
    {
        NSLog(@"%s",__func__);
    }

点击屏幕,打印内容如下,说明两个方法都触发了

2.2 手势状态

UIGestureRecognizer类中有如下一个属性,里面枚举了一些手势的当前状态:

objc 复制代码
@property(nonatomic,readonly) UIGestureRecognizerState state;

枚举值如下:

objc 复制代码
typedef NS_ENUM(NSInteger, UIGestureRecognizerState) {
    UIGestureRecognizerStatePossible,   // 默认的状态,这个时候的手势并没有具体的情形状态
    UIGestureRecognizerStateBegan,      // 手势开始被识别的状态
    UIGestureRecognizerStateChanged,    // 手势识别发生改变的状态
    UIGestureRecognizerStateEnded,      // 手势识别结束,将会执行触发的方法
    UIGestureRecognizerStateCancelled,  // 手势识别取消
    UIGestureRecognizerStateFailed,     // 识别失败,方法将不会被调用
    UIGestureRecognizerStateRecognized = UIGestureRecognizerStateEnded 
};

2.3 常用属性和方法

objc 复制代码
//手势代理 代理中有一些手势触发的方法,后面拿出来详细说明
@property(nullable,nonatomic,weak) id <UIGestureRecognizerDelegate> delegate; 
//设置手势是否有效
@property(nonatomic, getter=isEnabled) BOOL enabled;  
//获取手势所在的View
@property(nullable, nonatomic,readonly) UIView *view;          
//默认是YES。当识别到手势的时候,终止touchesCancelled:withEvent:或pressesCancelled:withEvent:发送的所有触摸事件。
@property(nonatomic) BOOL cancelsTouchesInView;     
//默认为NO ,在触摸开始的时候,就会发消息给事件传递链,如果设置为YES,在触摸没有被识别失败前,都不会给事件传递链发送消息。  
@property(nonatomic) BOOL delaysTouchesBegan;    
//默认为YES 。这个属性设置手势识别结束后,是立刻发送touchesEnded或pressesEnded消息到事件传递链或者等待一个很短的时间后,如果没有接收到新的手势识别任务,再发送。
@property(nonatomic) BOOL delaysTouchesEnded;         

@property(nonatomic, copy) NSArray<NSNumber *> *allowedTouchTypes NS_AVAILABLE_IOS(9_0); // Array of UITouchType's as NSNumbers.
@property(nonatomic, copy) NSArray<NSNumber *> *allowedPressTypes NS_AVAILABLE_IOS(9_0); // Array of UIPressTypes as NSNumbers.

//[A requireGestureRecognizerToFail:B]手势互斥 它可以指定当A手势发生时,即便A已经滿足条件了,也不会立刻触发,会等到指定的手势B确定失败之后才触发。
- (void)requireGestureRecognizerToFail:(UIGestureRecognizer *)otherGestureRecognizer;
//获取当前触摸的点
- (CGPoint)locationInView:(nullable UIView*)view;
//设置触摸点数
- (NSUInteger)numberOfTouches;
//获取某一个触摸点的触摸位置
- (CGPoint)locationOfTouch:(NSUInteger)touchIndex inView:(nullable UIView*)view; 

2.3.1 个别属性详解

其中几个BOOL值的属性,对于手势触发的控制也十分重要:

objc 复制代码
@property(nonatomic) BOOL cancelsTouchesInView;
@property(nonatomic) BOOL delaysTouchesBegan;
@property(nonatomic) BOOL delaysTouchesEnded;
objc 复制代码
- (void)viewDidLoad {
    [super viewDidLoad];
    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(pan:)];
    pan.cancelsTouchesInView = NO;
//    pan.delaysTouchesBegan = YES;
    [self.view addGestureRecognizer:pan];    
}
-(void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    NSLog(@"touchMoved手势触发");
}
-(void)pan:(UIPanGestureRecognizer *)pan{
    NSLog(@"pan手势触发");
}

pan.cancelsTouchesInView属性默认设置为YES

  • 如果识别到了手势,系统将会发送touchesCancelled:withEvent:消息在其时间传递链上,终止触摸事件的传递

  • 也就是说默认当识别到手势时,touch事件传递的方法将被终止而不执行,如果设置为NO,touch事件传递的方法仍然会被执行

  • 上例中我们使用了Pan手势touchesMoved两个触发方式

  • 当我们把cancelTouchesInView设置为NO时,在屏幕上滑动,两种方式都在触发,打印如下:

  • 而当我们将pan.cancelsTouchesInView = YES属性设置为YES时,打印结果如下

    • 我们发现touchesMoved的方法仍然被调用了,这是为什么呢?
    • 这就涉及到第二个属性delaysTouchesBegan
      • 这是因为手势识别是有一个过程的,拖拽手势需要一个很小的手指移动的过程才能被识别为拖拽手势
      • 而在一个手势触发之前,是会一并发消息给事件传递链的,所以才会有最开始的几个touchMoved方法被调用,当识别出拖拽手势以后,就会终止touch事件的传递。
      • delaysTouchesBgan属性用于控制这个消息的传递时机,默认这个属性为NO,此时在触摸开始的时候,就会发消息给事件传递链
      • 如果我们设置为YES,在触摸没有被识别失败前,都不会给事件传递链发送消息。
        因此当我们设置pan.delaysTouchesBegan = YES;时打印内容如下
      • 因为此时在拖拽手势识别失败之前,都不会给时间传递链发送消息,所以就不会在调用touchesMoved触发事件了
      • delaysTouchesEnded属性默认是YES,当设为YES时在手势识别结束后,会等待一个很短的时间,如果没有接收到新的手势识别任务,才会发送touchesEnded消息到事件传递链,设置为NO之后会立刻发送touchesEnded消息到事件传递链我们同样来看一个例子:
    objc 复制代码
    - (void)viewDidLoad {
        [super viewDidLoad];
        UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tap:)];
        tap.numberOfTapsRequired = 3;
    // tap.cancelsTouchesInView = NO;
    // tap.delaysTouchesBegan = YES;
        tap.delaysTouchesEnded = NO;
        [self.view addGestureRecognizer:tap];    
    }
    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        NSLog(@"touchBegan手势开始");
    }
    -(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
        NSLog(@"touchEnd手势触发结束");
    }
    -(void)tap:(UITapGestureRecognizer *)tap
    {
        NSLog(@"tap手势触发");
    }
  • tap.delaysTouchesEnded = NO;时,轻拍三下屏幕,打印如下

  • 我们发现我们每点击一下,都会立即发送touchesEnded消息到事件传递链。
    而当tap.delaysTouchesEnded = YES;时,轻拍三下屏幕,打印如下

  • 等三下轻拍手势识别结束后,才会发送消息到事件传递链。

2.3.2 重点方法详解-手势间的互斥处理

  • 同一个View上是可以添加多个手势对象的,默认这些手势是互斥的。一个手势触发了就会默认屏蔽其他相似的手势动作
  • 比如:
    • 单击和双击并存时,如果不做处理,它就只能发送出单击的消息。
    • 为了能够识别出双击手势,就需要用下面的方法一个特殊处理逻辑,即先判断手势是否是双击,在双击失效的情况下作为单击手势处理。
objc 复制代码
- (void)requireGestureRecognizerToFail:(UIGestureRecognizer *)otherGestureRecognizer;

**[A requireGestureRecognizerToFail:B] **

  • 它可以指定当A手势发生时,即便 A已经满足条件了,也不会立刻触发
  • 等到指定的手势B确定失败之后A才触发。 例子:
objc 复制代码
- (void)viewDidLoad {
    [super viewDidLoad];
    UITapGestureRecognizer *tap1 = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tap1:)];
    tap1.numberOfTapsRequired = 1;
    [self.view addGestureRecognizer:tap1];
    UITapGestureRecognizer *tap2 = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tap2:)];
    tap2.numberOfTapsRequired = 2;
    [self.view addGestureRecognizer:tap2];  
   //当tap2手势触发失败时才会触发tap1手势
    [tap1 requireGestureRecognizerToFail:tap2];
}
-(void)tap1:(UITapGestureRecognizer *)tap
{
    NSLog(@"tap1手势触发");
}
-(void)tap2:(UITapGestureRecognizer *)tap
{
    NSLog(@"tap2手势触发");
}

2.3.3 UIGestureRecognizerDelegate

前面我们提到过关于手势对象的协议代理,通过代理的回调,我们可以进行自定义手势,也可以处理一些复杂的手势关系,其中方法如下:

objc 复制代码
//手指触摸屏幕后回调的方法,返回NO则不再进行手势识别,方法触发等
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch;
//开始进行手势识别时调用的方法,返回NO则结束,不再触发手势
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer;
//是否支持多时候触发,返回YES,则可以多个手势一起触发方法,返回NO则为互斥
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer;
//下面这个两个方法也是用来控制手势的互斥执行的
//这个方法返回YES,第一个手势和第二个互斥时,第一个会失效
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer NS_AVAILABLE_IOS(7_0);
//这个方法返回YES,第一个和第二个互斥时,第二个会失效
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer NS_AVAILABLE_IOS(7_0);

3. UIGestureRecognizer子类及子类属性

除了UIGestureRecognizer中的方法和属性是所有子类通用的之外,UIGestureRecognizer子类中分别有不同的属性和方法来对应不同的手势。


UIGestureRecognizer的子类:

3.1 UITapGestureRecognizer|点击手势

点击手势十分简单,支持单击和多次点击,在我们手指触摸屏幕并抬起手指时会进行触发,其中有如下两个属性我们可以进行设置:

objc 复制代码
//设置点击次数,默认为单击
@property (nonatomic) NSUInteger  numberOfTapsRequired; 
//设置同时点击的手指数
@property (nonatomic) NSUInteger  numberOfTouchesRequired;

3.2 UIPinchGestureRecognizer|捏合手势

捏合手势是当我们双指捏合和扩张会触发动作的手势,我们可以设置的属性如下:

objc 复制代码
//设置缩放比例
@property (nonatomic)          CGFloat scale; 
//设置捏合速度
@property (nonatomic,readonly) CGFloat velocity;

3.3 UIPanGestureRecognzer|拖拽手势

当我们点中视图进行慢速拖拽时会触发拖拽手势的方法。

objc 复制代码
//设置触发拖拽的最少触摸点,默认为1
@property (nonatomic)          NSUInteger minimumNumberOfTouches; 
//设置触发拖拽的最多触摸点
@property (nonatomic)          NSUInteger maximumNumberOfTouches;  
//获取当前位置
- (CGPoint)translationInView:(nullable UIView *)view;            
//设置当前位置
- (void)setTranslation:(CGPoint)translation inView:(nullable UIView *)view;
//设置拖拽速度
- (CGPoint)velocityInView:(nullable UIView *)view;

3.4 UISwipeGestureRecognizer|滑动手势

滑动手势和拖拽手势的不同之处在于滑动手势更快,而拖拽比较慢。

objc 复制代码
//设置触发滑动手势的触摸点数
@property(nonatomic) NSUInteger                        numberOfTouchesRequired; 
//设置滑动方向
@property(nonatomic) UISwipeGestureRecognizerDirection direction;  
//枚举如下
typedef NS_OPTIONS(NSUInteger, UISwipeGestureRecognizerDirection) {
    UISwipeGestureRecognizerDirectionRight = 1 << 0,
    UISwipeGestureRecognizerDirectionLeft  = 1 << 1,
    UISwipeGestureRecognizerDirectionUp    = 1 << 2,
    UISwipeGestureRecognizerDirectionDown  = 1 << 3
};

3.5 UIRotationGestureRecognizer|旋转手势

进行旋转动作时触发手势方法。

objc 复制代码
//设置旋转角度
@property (nonatomic)          CGFloat rotation;
//设置旋转速度 
@property (nonatomic,readonly) CGFloat velocity;

3.6 UILongPressGestureRecognizer|长按手势

进行长按的时候触发的手势方法。

objc 复制代码
//设置触发前的点击次数
@property (nonatomic) NSUInteger numberOfTapsRequired;    
//设置触发的触摸点数
@property (nonatomic) NSUInteger numberOfTouchesRequired; 
//设置最短的长按时间
@property (nonatomic) CFTimeInterval minimumPressDuration; 
//设置在按触时时允许移动的最大距离 默认为10像素
@property (nonatomic) CGFloat allowableMovement;

3.7 自定义手势

  • 在.m文件中需要引入#import <UIKit/UIGestureRecognizerSubclass.h>。
  • 自定义手势继承:UIGestureRecognizer
  • 实现下面的方法,在以下方法中判断自定义手势是否实现:
objc 复制代码
-- touchesBegan:withEvent:  
-- touchesMoved:withEvent:  
-- touchesEnded:withEvent:  
- touchesCancelled:withEvent: 

五、事件交互|UIResponder

1. UIResponder简介

  • UIResponder是iOS中所有响应者对象的基类 ,包括视图View视图控制器ViewController应用程序对象Application等。
  • UIResponder负责响应并处理来自用户的触摸事件按压事件加速事件远程控制事件键盘事件其他事件

2. UIResponderStandardEditActions协议介绍

UIResponder遵守了UIResponderStandardEditActions协议,且内部对方法进行了实现

objc 复制代码
#pragma mark - UIResponderStandardEditActions协议定义
 
@protocol UIResponderStandardEditActions <NSObject>
@optional
/** 剪切事件 */
- (void)cut:(nullable id)sender NS_AVAILABLE_IOS(3_0);
/** 复制事件 */
- (void)copy:(nullable id)sender NS_AVAILABLE_IOS(3_0);
/** 粘贴事件 */
- (void)paste:(nullable id)sender NS_AVAILABLE_IOS(3_0);
/** 选择事件 */
- (void)select:(nullable id)sender NS_AVAILABLE_IOS(3_0);
/** 全选事件 */
- (void)selectAll:(nullable id)sender NS_AVAILABLE_IOS(3_0);
/** 删除事件 */
- (void)delete:(nullable id)sender NS_AVAILABLE_IOS(3_2);
/** 从左到右写入字符串(居左) */
- (void)makeTextWritingDirectionLeftToRight:(nullable id)sender NS_AVAILABLE_IOS(5_0);
/** 从右到左写入字符串(居右) */
- (void)makeTextWritingDirectionRightToLeft:(nullable id)sender NS_AVAILABLE_IOS(5_0);
/** 切换字体为黑体(粗体) */
- (void)toggleBoldface:(nullable id)sender NS_AVAILABLE_IOS(6_0);
/** 切换字体为斜体 */
- (void)toggleItalics:(nullable id)sender NS_AVAILABLE_IOS(6_0);
/** 给文字添加下划线 */
- (void)toggleUnderline:(nullable id)sender NS_AVAILABLE_IOS(6_0);
 
/** 增加字体大小 */
- (void)increaseSize:(nullable id)sender NS_AVAILABLE_IOS(7_0);
/** 减小字体大小 */
- (void)decreaseSize:(nullable id)sender NS_AVAILABLE_IOS(7_0);
 
@end
 

3. UIResponder 常用属性介绍

objc 复制代码
#pragma mark - 响应者相关方法
 
/** 获取下一个响应者 */
@property(nonatomic, readonly, nullable) UIResponder *nextResponder;

 
/** 是否允许成为第一响应者。默认返回NO */
@property(nonatomic, readonly) BOOL canBecomeFirstResponder;
/** 设置成为第一响应者 */
- (BOOL)becomeFirstResponder;
 
/** 是否允许放弃第一响应者。默认返回YES */
@property(nonatomic, readonly) BOOL canResignFirstResponder;
/** 设置放弃第一响应者 */
- (BOOL)resignFirstResponder;
 
/** 判断对象是否是第一响应者 */
@property(nonatomic, readonly) BOOL isFirstResponder;

4. UIResponder|触摸事件相关回调方法

objc 复制代码
#pragma mark - 触摸相关方法,一般用于响应屏幕触摸
/** 手指按下时响应 */
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
/** 手指移动时响应 */
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
/** 手指抬起时响应 */
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
/** 取消(意外中断, 如:电话, 系统警告窗等) */
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
/** 3DTouch响应(iOS9.1后使用) */
- (void)touchesEstimatedPropertiesUpdated:(NSSet<UITouch *> *)touches NS_AVAILABLE_IOS(9_1);

5. UIResponder|按压事件相关回调方法

objc 复制代码
#pragma mark - 深按相关方法,一般用于遥控器按键响应
/** 手指按压开始时响应 */
- (void)pressesBegan:(NSSet<UIPress *> *)presses withEvent:(nullable UIPressesEvent *)event NS_AVAILABLE_IOS(9_0);
/** 手指按压位置移动时响应 */
- (void)pressesChanged:(NSSet<UIPress *> *)presses withEvent:(nullable UIPressesEvent *)event NS_AVAILABLE_IOS(9_0);
/** 手指抬起接受按压时响应 */
- (void)pressesEnded:(NSSet<UIPress *> *)presses withEvent:(nullable UIPressesEvent *)event NS_AVAILABLE_IOS(9_0);
/** 按压取消(意外中断, 如:电话, 系统警告窗等) */
- (void)pressesCancelled:(NSSet<UIPress *> *)presses withEvent:(nullable UIPressesEvent *)event NS_AVAILABLE_IOS(9_0);

6. UIResponder|加速事件相关回调方法

objc 复制代码
#pragma mark - 加速相关方法,一般用于摇一摇、运动事件监听等
/** 开始加速 */
- (void)motionBegan:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event NS_AVAILABLE_IOS(3_0);
/** 结束加速 */
- (void)motionEnded:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event NS_AVAILABLE_IOS(3_0);
/** 加速取消(意外中断, 如:电话, 系统警告窗等) */
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event NS_AVAILABLE_IOS(3_0);

7. UIResponder|远程控制事件相关回调方法

objc 复制代码
/** 远程控制事件 */
- (void)remoteControlReceivedWithEvent:(nullable UIEvent *)event NS_AVAILABLE_IOS(4_0);

8. UIResponder|其它方法

objc 复制代码
//
//  UIResponder.h
//  UIKit
//
//  Copyright (c) 2005-2018 Apple Inc. All rights reserved.
//
//  详解 UIResponder.h
//  Version iOS 10.3
//
 

#import <Foundation/Foundation.h>
#import <UIKit/UIKitDefines.h>
#import <UIKit/UIEvent.h>
#import <UIKit/UIKeyCommand.h>
#import <UIKit/UIPasteConfigurationSupporting.h>
#import <UIKit/UIUserActivity.h>
NS_HEADER_AUDIT_BEGIN(nullability, sendability) 

@class UIPress;
@class UIPressesEvent;
 

#pragma mark - UIResponder类定义
 
NS_CLASS_AVAILABLE_IOS(2_0) NS_SWIFT_UI_ACTOR
@interface UIResponder : NSObject <UIResponderStandardEditActions>
 ...
/** 返回UIMenuController需要显示的控件(如:复制,粘贴等) */
- (BOOL)canPerformAction:(SEL)action withSender:(nullable id)sender NS_AVAILABLE_IOS(3_0);
 
/** 返回响应的操作目标对象 */
- (nullable id)targetForAction:(SEL)action withSender:(nullable id)sender NS_AVAILABLE_IOS(7_0);

/** 获取响应链就近共享撤消管理 */
@property(nullable, nonatomic,readonly) NSUndoManager *undoManager NS_AVAILABLE_IOS(3_0);
 
@end
 
/** 快捷主键枚举 */
typedef NS_OPTIONS(NSInteger, UIKeyModifierFlags) {
    UIKeyModifierAlphaShift     = 1 << 16,  //!< Alpha+Shift键.
    UIKeyModifierShift          = 1 << 17,  //!< Shift键.
    UIKeyModifierControl        = 1 << 18,  //!< Control键.
    UIKeyModifierAlternate      = 1 << 19,  //!< Alt键.
    UIKeyModifierCommand        = 1 << 20,  //!< Command键.
    UIKeyModifierNumericPad     = 1 << 21,  //!< Num键.
} NS_ENUM_AVAILABLE_IOS(7_0);
 
#pragma mark - 快捷键对象
 
NS_CLASS_AVAILABLE_IOS(7_0) @interface UIKeyCommand : NSObject <NSCopying, NSSecureCoding>
 
/** 初始化对象 */
- (instancetype)init NS_DESIGNATED_INITIALIZER;
/** 初始化对象 */
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER;
 
/** 获取快捷辅键(如快捷命令【Command+A】中的 A 键) */
@property (nonatomic,readonly) NSString *input;
/** 获取快捷主键(如快捷命令【Command+A】中的 Command 键) */
@property (nonatomic,readonly) UIKeyModifierFlags modifierFlags;
/** 显示给用户的快捷键标题 */
@property (nullable,nonatomic,copy) NSString *discoverabilityTitle NS_AVAILABLE_IOS(9_0);
 
/** 创建一个快捷键命令 */
+ (UIKeyCommand *)keyCommandWithInput:(NSString *)input modifierFlags:(UIKeyModifierFlags)modifierFlags action:(SEL)action;
 
/** 创建一个快捷键命令 */
+ (UIKeyCommand *)keyCommandWithInput:(NSString *)input modifierFlags:(UIKeyModifierFlags)modifierFlags action:(SEL)action discoverabilityTitle:(NSString *)discoverabilityTitle NS_AVAILABLE_IOS(9_0);
 
@end
 
#pragma mark - 响应快捷命令
 
@interface UIResponder (UIResponderKeyCommands)
/** 返回快捷键命令数组 */
@property (nullable,nonatomic,readonly) NSArray<UIKeyCommand *> *keyCommands NS_AVAILABLE_IOS(7_0);
@end
 
@class UIInputViewController;
@class UITextInputMode;
@class UITextInputAssistantItem;
 
#pragma mark - 输入视图
 
@interface UIResponder (UIResponderInputViewAdditions)
 
/** 键盘输入视图(系统默认的,可以自定义) */
@property (nullable, nonatomic, readonly, strong) __kindof UIView *inputView NS_AVAILABLE_IOS(3_2);
/** 弹出键盘时附带的视图 */
@property (nullable, nonatomic, readonly, strong) __kindof UIView *inputAccessoryView NS_AVAILABLE_IOS(3_2);
 
/** 输入助手配置键盘的快捷方式栏时使用 */
@property (nonnull, nonatomic, readonly, strong) UITextInputAssistantItem *inputAssistantItem NS_AVAILABLE_IOS(9_0) __TVOS_PROHIBITED __WATCHOS_PROHIBITED;
 
/** 键盘输入视图控制器 */
@property (nullable, nonatomic, readonly, strong) UIInputViewController *inputViewController NS_AVAILABLE_IOS(8_0);
/** 弹出键盘时附带的视图的视图控制器 */
@property (nullable, nonatomic, readonly, strong) UIInputViewController *inputAccessoryViewController NS_AVAILABLE_IOS(8_0);
 
/** 文本输入模式 */
@property (nullable, nonatomic, readonly, strong) UITextInputMode *textInputMode NS_AVAILABLE_IOS(7_0);
 
/** 文本输入模式标识 */
@property (nullable, nonatomic, readonly, strong) NSString *textInputContextIdentifier NS_AVAILABLE_IOS(7_0);
/** 根据设置的标识清除指定的文本输入模式 */
+ (void)clearTextInputContextIdentifier:(NSString *)identifier NS_AVAILABLE_IOS(7_0);
 
/** 重新刷新键盘输入视图 */
- (void)reloadInputViews NS_AVAILABLE_IOS(3_2);
 
@end
 
/** 特殊快捷辅键定义 */
UIKIT_EXTERN NSString *const UIKeyInputUpArrow         NS_AVAILABLE_IOS(7_0); //!< 上按键.
UIKIT_EXTERN NSString *const UIKeyInputDownArrow       NS_AVAILABLE_IOS(7_0); //!< 下按键.
UIKIT_EXTERN NSString *const UIKeyInputLeftArrow       NS_AVAILABLE_IOS(7_0); //!< 左按键.
UIKIT_EXTERN NSString *const UIKeyInputRightArrow      NS_AVAILABLE_IOS(7_0); //!< 右按键
UIKIT_EXTERN NSString *const UIKeyInputEscape          NS_AVAILABLE_IOS(7_0); //!< Esc按键.
 
#pragma mark - 响应者活动
 
@interface UIResponder (ActivityContinuation)
/** 用户活动 */
@property (nullable, nonatomic, strong) NSUserActivity *userActivity NS_AVAILABLE_IOS(8_0);
/** 更新用户活动 */
- (void)updateUserActivityState:(NSUserActivity *)activity NS_AVAILABLE_IOS(8_0);
/** 恢复用户活动 */
- (void)restoreUserActivityState:(NSUserActivity *)activity NS_AVAILABLE_IOS(8_0);
@end
 
NS_ASSUME_NONNULL_END
 
 

9. UIResponder使用Demo

1. 通过响应者链查找视图的视图控制器

objc 复制代码
/**
 *  查找视图的视图控制器
 *
 *  @param view 视图
 *
 *  @return 返回视图的控制器
 */
- (UIViewController *)getControllerFromView:(UIView *)view {
    // 遍历响应者链。返回第一个找到视图控制器
    UIResponder *responder = view;
    while ((responder = [responder nextResponder])){
        if ([responder isKindOfClass: [UIViewController class]]){
            return (UIViewController *)responder;
        }
    }
    // 如果没有找到则返回nil
    return nil;
}
   通过响应链查找视图控制器,nextResponder获取下一个响应者,响应者顺序为:

2. 设置与取消第一响应者

objc 复制代码
//
//  FirstResponderView.m
//  ResponderDemo
//
//  Created by VanZhang on 2017/5/12.
//  Copyright © 2017年 . All rights reserved.
//
 
#import "FirstResponderView.h"
 
@implementation FirstResponderView
 
/** 演示设置为第一响应者 */
- (void)setBecomeFirstResponder {
    // 判断对象是否已经是第一响应者
    if ([self isFirstResponder]) {
        return;
    }
    // 判断对象是否允许成为第一响应者
    if ([self canBecomeFirstResponder]) {
        // 设置成为第一响应者
        [self becomeFirstResponder];
    }
}
 
/** 演示放弃第一响应者 */
- (void)setResignFirstResponder {
    // 判断对象是否不是第一响应者
    if (![self isFirstResponder]) {
        return;
    }
    // 判断对象是否允许放弃第一响应者
    if ([self canResignFirstResponder]) {
        // 设置放弃第一响应者
        [self resignFirstResponder];
    }
}
 
/** 重写方法,允许对象成为第一响应者 */
- (BOOL)canBecomeFirstResponder {
    return YES;
}
 
@end
      UIView默认不允许设置为第一响应者,因此设置UIView为第一响应者需要重写canBecomeFirstResponder方法并返回YES。 设置为第一响应者后,对象则可以接受远程控制事件进行处理(如耳机线控)。 UITextField、UITextView成为第一响应者后会弹出输入键盘,取消第一响应者则会隐藏输入键盘。

3. 触摸相关方法,一般用于响应屏幕触摸

objc 复制代码
/** 手指按下时响应 */
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event {
    [super touchesBegan:touches withEvent:event];
    NSLog(@"--->手指按下时响应");
}
 
/** 手指移动时响应 */
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event {
    [super touchesMoved:touches withEvent:event];
    NSLog(@"--->手指移动时响应");
}
 
/** 手指抬起时响应 */
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event {
    [super touchesEnded:touches withEvent:event];
    NSLog(@"--->手指抬起时响应");
}
 
/** 触摸取消(意外中断, 如:电话, Home键退出等) */
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event {
    [super touchesCancelled:touches withEvent:event];
    NSLog(@"--->取消触摸响应");
}

4. 加速相关方法,一般用于摇一摇、运动事件监听等

objc 复制代码
/** 开始加速 */
- (void)motionBegan:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event NS_AVAILABLE_IOS(3_0) {
    [super motionBegan:motion withEvent:event];
    NSLog(@"--->开始加速");
}
 
/** 结束加速 */
- (void)motionEnded:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event NS_AVAILABLE_IOS(3_0) {
    [super motionEnded:motion withEvent:event];
    NSLog(@"--->结束加速");
}
 
/** 加速取消(意外中断, 如:电话, Home键退出等) */
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event NS_AVAILABLE_IOS(3_0) {
    [super motionCancelled:motion withEvent:event];
    NSLog(@"--->加速取消");
}

5. 远程控制方法,一般用于耳机线控

objc 复制代码
//
//  AudioView.m
//  ResponderDemo
//
//  Created by VanZhang on 2017/5/12.
//  Copyright © 2017年 . All rights reserved.
//
 
#import "AudioView.h"
#import <AVFoundation/AVFoundation.h>
 
@implementation AudioView
 
- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // 启动接受远程事件
        [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
        // 设置成为第一响应者
        [self becomeFirstResponder];
        // 播放一段静音文件,使APP获取音频的控制权
        NSURL *audioURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"mute_60s" ofType:@"mp3"]];
        AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:audioURL error:nil];
        [audioPlayer play];
    }
    return self;
}
 
/** 允许对象成为第一响应者 */
- (BOOL)canBecomeFirstResponder {
    return YES;
}
 
/** 远程控制事件响应 */
- (void)remoteControlReceivedWithEvent:(UIEvent *)receivedEvent {
    NSLog(@"--->耳机线控响应");
}
 
- (void)dealloc {
    // 停止接受远程事件
    [[UIApplication sharedApplication] endReceivingRemoteControlEvents];
    // 放弃第一响应者
    [self resignFirstResponder];
}
 
@end

耳机线控要注意三点要素:

  • (1)启动接受远程事件:[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];

  • (2)设置成为第一响应者(UIViewControllerAppDelegate中不需要设置)

    objc 复制代码
    // 设置成为第一响应者
    [self becomeFirstResponder];
    
    /** 允许对象成为第一响应者 */
    - (BOOL)canBecomeFirstResponder {
        return YES;
    }
  • (3)获取音频的控制权

    objc 复制代码
    // 播放一段静音文件,使APP获取音频的控制权
    NSURL *audioURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"mute_60s" ofType:@"mp3"]];
    AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:audioURL error:nil];
    [audioPlayer play];

6、在UILabel中实现长按菜单(复制、粘贴等)

objc 复制代码
//
//  MenuLabel.m
//  ResponderDemo
//
//
 
#import "MenuLabel.h"
 
@implementation MenuLabel
 
- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // 启用用户交互
        self.userInteractionEnabled = YES;
        // 添加长按手势
        UILongPressGestureRecognizer *longPressGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPressMenu:)];
        longPressGesture.minimumPressDuration = 0.2;
        [self addGestureRecognizer:longPressGesture];
    }
    return self;
}
 
/** 允许对象成为第一响应者 */
- (BOOL)canBecomeFirstResponder {
    return YES;
}
 
/** 长按响应 */
- (void)longPressMenu:(UILongPressGestureRecognizer *)sender {
    if (sender.state == UIGestureRecognizerStateBegan) {
        // 设置成为第一响应者
        [self becomeFirstResponder];
        // 显示菜单
        UIMenuController *menuCtrl = [UIMenuController sharedMenuController];
        [menuCtrl setTargetRect:self.frame inView:self.superview];
        [menuCtrl setMenuVisible:YES animated:YES];
    }
}
 
/** 返回需要显示的菜单按钮 */
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
    // 只显示复制、粘贴按钮
    if (action == @selector(copy:) || action == @selector(paste:)) {
        return YES;
    }
    return NO;
}
 
/** 实现复制方法 */
- (void)copy:(id)sender {
    UIPasteboard *paste = [UIPasteboard generalPasteboard];
    paste.string = self.text;
}
 
/** 实现粘贴方法 */
- (void)paste:(id)sender {
    UIPasteboard *paste = [UIPasteboard generalPasteboard];
    self.text = paste.string;
}
 
@end

为UILabel添加长按菜单需要注意几点:

  • (1)启用用户交互:self.userInteractionEnabled = YES;

  • (2)在显示菜单之前设置对象成为第一响应者(UIViewControllerAppDelegate中不需要设置)

    objc 复制代码
    /** 允许对象成为第一响应者 */
    - (BOOL)canBecomeFirstResponder {
        return YES;
    }
    
    // 设置成为第一响应者
    [self becomeFirstResponder];

-(3)返回菜单需要显示的按钮,并重写实现对应方法 ```objc

less 复制代码
/** 返回需要显示的菜单按钮 */
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
    // 只显示复制、粘贴按钮
    if (action == @selector(copy:) || action == @selector(paste:)) {
        return YES;
    }
    return NO;
}

/** 实现复制方法 */
- (void)copy:(id)sender {
    UIPasteboard *paste = [UIPasteboard generalPasteboard];
    paste.string = self.text;
}

/** 实现粘贴方法 */
- (void)paste:(id)sender {
    UIPasteboard *paste = [UIPasteboard generalPasteboard];
    self.text = paste.string;
}
```
  • (4)注册长按手势,显示菜单

    objc 复制代码
    // 添加长按手势
    UILongPressGestureRecognizer *longPressGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPressMenu:)];
    longPressGesture.minimumPressDuration = 0.2;
    [self addGestureRecognizer:longPressGesture];
    
    /** 长按响应 */
    - (void)longPressMenu:(UILongPressGestureRecognizer *)sender {
        if (sender.state == UIGestureRecognizerStateBegan) {
            // 设置成为第一响应者
            [self becomeFirstResponder];
            // 显示菜单
            UIMenuController *menuCtrl = [UIMenuController sharedMenuController];
            [menuCtrl setTargetRect:self.frame inView:self.superview];
            [menuCtrl setMenuVisible:YES animated:YES];
        }
    }

7、使用NSUndoManager实现画板撤销/重做功能

objc 复制代码
/** ==============DrawingBoardView.h文件=================== */
 
#import <UIKit/UIKit.h>
 
/** 画板View */
@interface DrawingBoardView : UIView
 
@end
 
 
/** 划线Model */
@interface LineModel : NSObject
 
@property (nonatomic) CGPoint begin;
@property (nonatomic) CGPoint end;
 
@end
 
 
/** ==============DrawingBoardView.m文件=================== */
 
#import "DrawingBoardView.h"
 
/** 画板View */
@interface DrawingBoardView ()
 
@property (nonatomic, strong) LineModel *currentLine;
@property (nonatomic, strong) NSMutableArray<LineModel *> *toucheArray;
 
@end
 
@implementation DrawingBoardView
 
 
- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self initSubView];
        self.backgroundColor = [UIColor whiteColor];
        self.toucheArray = [NSMutableArray array];
    }
    return self;
}
 
/** 绘制画板 */
- (void)drawRect:(CGRect)rect {
    // 获得上下文
    CGContextRef context = UIGraphicsGetCurrentContext();
    // 设置样式
    CGContextSetLineCap(context, kCGLineCapSquare);
    // 设置宽度
    CGContextSetLineWidth(context, 5.0);
    // 设置颜色
    CGContextSetStrokeColorWithColor(context, [[UIColor redColor] CGColor]);
    
    for (LineModel *line in self.toucheArray) {
        // 开始绘制
        CGContextBeginPath(context);
        // 移动画笔到起点
        CGContextMoveToPoint(context, line.begin.x, line.begin.y);
        // 添加下一点
        CGContextAddLineToPoint(context, line.end.x, line.end.y);
        // 绘制完成
        CGContextStrokePath(context);
    }
}
 
/** 划线开始 */
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    // 标记开始撤销分组
    [self.undoManager beginUndoGrouping];
    
    for (UITouch *touch in touches) {
        // 记录起始点
        CGPoint locTouch = [touch locationInView:self];
        _currentLine = [[LineModel alloc] init];
        _currentLine.begin = locTouch;
        _currentLine.end = locTouch;
    }
    
}
 
/** 划线移动 */
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    for (UITouch *touch in touches) {
        // 添加线条
        CGPoint locTouch = [touch locationInView:self];
        _currentLine.end = locTouch;
        [self addLine:_currentLine];
        // 当前线条
        _currentLine = [[LineModel alloc] init];
        _currentLine.begin = locTouch;
        _currentLine.end = locTouch;
    }
}
 
/** 划线结束 */
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    // 结束标记撤销分组
    [self.undoManager endUndoGrouping];
}
 
/** 添加划线 */
- (void)addLine:(LineModel *)line
{
    // 添加划线并重绘画板
    [self.toucheArray addObject:line];
    [self setNeedsDisplay];
    // 注册撤销方法
    [[self.undoManager prepareWithInvocationTarget:self] removeLine:line];
}
 
/** 移除划线 */
- (void)removeLine:(LineModel *)line
{
    if ([self.toucheArray containsObject:line]) {
        // 移除划线并重绘画板
        [self.toucheArray removeObject:line];
        [self setNeedsDisplay];
        // 注册撤销方法
        [[self.undoManager prepareWithInvocationTarget:self] addLine:line];
    }
}
 
/** 撤销按钮点击响应 */
- (void)undoButtonAction:(id)sender {
    if ([self.undoManager canUndo]) {
        [self.undoManager undo];
    }
}
 
/** 重做按钮点击响应 */
- (void)redoButtonAction:(id)sender {
    if ([self.undoManager canRedo]) {
        [self.undoManager redo];
    }
}
 
/** 初始化子控件 */
- (void)initSubView {
    // 撤销按钮
    UIButton *undoButton = [UIButton buttonWithType:UIButtonTypeSystem];
    undoButton.frame = CGRectMake(0, 64, 70, 50);
    [undoButton setTitle:@"undo撤销" forState:UIControlStateNormal];
    [undoButton sizeToFit];
    [undoButton addTarget:self action:@selector(undoButtonAction:) forControlEvents:UIControlEventTouchUpInside];
    [self addSubview:undoButton];
    // 重做按钮
    UIButton *redoButton = [UIButton buttonWithType:UIButtonTypeSystem];
    redoButton.frame = CGRectMake(CGRectGetWidth(self.frame)-70, 64, 70, 50);
    [redoButton setTitle:@"redo重做" forState:UIControlStateNormal];
    [redoButton sizeToFit];
    [redoButton addTarget:self action:@selector(redoButtonAction:) forControlEvents:UIControlEventTouchUpInside];
    [self addSubview:redoButton];
}
 
@end

实现撤销/重做注意以下几点:

  • (1)在调用方法时需要添加注册一个对应的撤销方法

    objc 复制代码
    // 注册撤销方法
    [[self.undoManager prepareWithInvocationTarget:self] removeLine:line];
  • (2)撤销/ 重做只需要调用undoManager中的相应方法即可

    objc 复制代码
        /** 撤销按钮点击响应 */
        - (void)undoButtonAction:(id)sender {
            if ([self.undoManager canUndo]) {
                [self.undoManager undo];
            }
        }
    
        /** 重做按钮点击响应 */
        - (void)redoButtonAction:(id)sender {
            if ([self.undoManager canRedo]) {
                [self.undoManager redo];
            }
        }
  • (3)如果需要多个动作一起撤销则需要标记分组

    objc 复制代码
       // 标记开始撤销分组
        [self.undoManager beginUndoGrouping];
        // 结束标记撤销分组
        [self.undoManager endUndoGrouping];

8、自定义快捷键

objc 复制代码
//
//  KeyCommandView.m
//  ResponderDemo
//
//  Created by VanZhang on 2017/5/17.
//  Copyright © 2017年 . All rights reserved.
//
 
#import "KeyCommandView.h"
 
@implementation KeyCommandView
 
- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // 设置成为第一响应者
        [self becomeFirstResponder];
    }
    return self;
}
 
/** 允许对象成为第一响应者 */
- (BOOL)canBecomeFirstResponder {
    return YES;
}
 
/** 返回快捷命令数组 */
-(NSArray<UIKeyCommand *> *)keyCommands {
    return @[
             [UIKeyCommand keyCommandWithInput:UIKeyInputEscape modifierFlags:UIKeyModifierShift action:@selector(pressedShiftAndEscapeKey:) discoverabilityTitle:@"自定义[Shift+Esc]快捷键"],
             [UIKeyCommand keyCommandWithInput:@"a" modifierFlags:UIKeyModifierShift action:@selector(pressedShiftAndAKey:) discoverabilityTitle:@"自定义[Shift+A]快捷键"]
             ];
}
 
/** Shift+Esc快捷命令响应 */
-(void)pressedShiftAndEscapeKey:(UIKeyCommand *)keyCommand {
    UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:keyCommand.discoverabilityTitle message:[NSString stringWithFormat:@"按下快捷辅键:[%@]", keyCommand.input] delegate:nil cancelButtonTitle:@"确定" otherButtonTitles:nil];
    [alertView show];
}
 
/** Shift+A快捷命令响应 */
-(void)pressedShiftAndAKey:(UIKeyCommand *)keyCommand {
    UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:keyCommand.discoverabilityTitle message:[NSString stringWithFormat:@"按下快捷辅键:[%@]", keyCommand.input] delegate:nil cancelButtonTitle:@"确定" otherButtonTitles:nil];
    [alertView show];
}
 
@end
自定义快捷键需要注意两点:
(1)设置对象成为第一响应者(UIViewController,AppDelegate中不需要设置)

// 设置成为第一响应者
[self becomeFirstResponder];
 
/** 允许对象成为第一响应者 */
- (BOOL)canBecomeFirstResponder {
    return YES;
}
(2)重写 keyCommands 返回快捷命令组合
/** 返回快捷命令数组 */
-(NSArray<UIKeyCommand *> *)keyCommands {
    return @[
             [UIKeyCommand keyCommandWithInput:UIKeyInputEscape modifierFlags:UIKeyModifierShift action:@selector(pressedShiftAndEscapeKey:) discoverabilityTitle:@"自定义[Shift+Esc]快捷键"],
             [UIKeyCommand keyCommandWithInput:@"a" modifierFlags:UIKeyModifierShift action:@selector(pressedShiftAndAKey:) discoverabilityTitle:@"自定义[Shift+A]快捷键"]
             ];
}

9、自定义UITextField输入键盘

objc 复制代码
//
//  CustomInputView.m
//  ResponderDemo
//
//  Created by VanZhang on 2017/5/18.
//  Copyright © 2017年 . All rights reserved.
//
 
#import "CustomInputView.h"
 
#define MAIN_SCREEN_WIDTH [[UIScreen mainScreen] bounds].size.width   //!< 屏幕的Width
 
@interface CustomInputView ()
 
@property (nonatomic, strong) UITextField *textField;
@property (nonatomic, strong) UIView *customInputView;
@property (nonatomic, strong) UIToolbar *customAccessoryView;
 
@end
 
@implementation CustomInputView
 
- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // 添加TextField
        [self addSubview:self.textField];
    }
    return self;
}
 
/** 懒加载textField */
- (UITextField *)textField {
    if (!_textField) {
        // 初始化textField
        _textField = [[UITextField alloc]initWithFrame:CGRectMake(50, 100, MAIN_SCREEN_WIDTH - 100, 30)];
        _textField.borderStyle = UITextBorderStyleRoundedRect;
        _textField.placeholder = @"测试";
        // 设置自定义键盘View
        _textField.inputView = self.customInputView;
        _textField.inputAccessoryView = self.customAccessoryView;
    }
    return _textField;
}
 
/** 懒加载customInputView */
- (UIView *)customInputView {
    if (!_customInputView) {
        _customInputView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, MAIN_SCREEN_WIDTH, 220)];
        _customInputView.backgroundColor = [UIColor lightGrayColor];
        UILabel *label = [[UILabel alloc]initWithFrame:CGRectMake(0, 100, MAIN_SCREEN_WIDTH, 40)];
        label.textAlignment = NSTextAlignmentCenter;
        label.text = @"自定义inputView";
        [_customInputView addSubview:label];
    }
    return _customInputView;
}
 
/** 懒加载customAccessoryView */
- (UIToolbar *)customAccessoryView {
    if (!_customAccessoryView) {
        _customAccessoryView = [[UIToolbar alloc]initWithFrame:CGRectMake(0, 0, MAIN_SCREEN_WIDTH, 40)];
        _customAccessoryView.barTintColor = [UIColor orangeColor];
        UIBarButtonItem *space = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
        UIBarButtonItem *done = [[UIBarButtonItem alloc]initWithTitle:@"完成" style:UIBarButtonItemStyleDone target:self action:@selector(done)];
        [_customAccessoryView setItems:@[space, space, done]];
    }
    return _customAccessoryView;
}
 
/** 响应完成按钮 */
- (void)done {
    [self.textField resignFirstResponder];
}
 
 
@end 

六、UIApplication

UIApplication 是iOS应用程序的核心类之一,承担着管理应用程序生命周期事件处理应用程序级别操作的重要角色,为应用程序的正常运行和用户体验提供了基础支持。

以下是UIApplication的简介:

  • UIApplication对象是应用程序的象征
  • 利用UIApplication对象能进行一些应用级别的操作
  • 每一个应用程序都有自己的第一个UI对象就是UIApplication对象。
  • 通过UIApplication *app = [UIApplication sharedApplication];可以获得这个单例对象。

1. UIApplication对象的创建时机

那么UIApplication对象是什么时候被创建的呢?

我们找到程序的入口main.m 阅读 main 函数

objc 复制代码
int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

我们发现程序一开始调用了UIApplicationMain方法,并且还有4个参数:

  • argc The count of arguments in argv; this usually is the corresponding parameter to main.
  • argv A variable list of arguments; this usually is the corresponding parameter to main.
  • principalClassName The name of the UIApplication class or subclass. If you specify nil, UIApplication is assumed.
  • delegateClassName The name of the class from which the application delegate is instantiated. If principalClassName designates a subclass of UIApplication, you may designate the subclass as the delegate; the subclass instance receives the application-delegate messages. Specify nil if you load the delegate object from your application's main nib file.

  • argc:系统或者用户传入的参数
  • argv:系统或用户传入的实际参数 重点放在第三、四个参数
  • 第三个参数 nil:代表UIApplication类名或者子类名称,nil 相当于 @"UIApplicaiton";
  • 第四个参数:代表UIApplicaiton的代理名称 NSStringFromClass([AppDelegate class] 相当于 @"AppDelegate";

2. 了解应用程序启动的过程

此时我们可以根据UIApplicationMain函数了解程序启动的过程:

根据传递的类名创建UIApplication对象,这是第一个对象 2. 创建UIApplication代理对象,并给UIApplicaiton对象设置代理

  1. 开启主线程运行循环 main events loop处理事件,保持程序一直运行
  2. 加载info.plist,判断是否指定mian(xib 或者 storyboard)如果指定就去加载
  3. ...

3. AppDelegate回调方法

在创建UIApplication代理对象,并给UIApplicaiton对象设置代理AppDelegate之后,UIApplication应用级事件 的管理就通过遵守了UIApplicationDelegate协议的代理对象AppDelegate的回调来分发出去。

因此,我们应该对 UIApplicationDelegate协议 中的常用回调方法有所了解:

AppDelegate的回调方法:

3.1 App初始化:

application(_:willFinishLaunchingWithOptions:): * 当应用程序即将完成启动过程时调用,但在应用程序界面显示之前。 * 可以在此方法中进行应用程序的初始化设置和准备工作。 2. application(_:didFinishLaunchingWithOptions:): * 当应用程序完成启动过程时调用,此时应用程序已经准备好显示界面。 * 可以在此方法中进行应用程序的最终设置和准备工作。

对应的通知的名称:

3.2 应用程序生命周期管理:

UIApplication 负责管理应用程序的生命周期 ,包括应用程序的启动运行进入后台恢复等阶段:

applicationDidBecomeActive(_:): * 当应用程序从后台切换到前台并变为活动状态时调用。 * 可以在此方法中恢复应用程序的运行状态和处理需要立即执行的任务。 2. applicationWillResignActive(_:): * 当应用程序将要从活动状态切换到非活动状态时调用,如来电、弹出系统警告等情况。 * 可以在此方法中暂停应用程序的运行状态和处理需要延迟执行的任务。 3. applicationDidEnterBackground(_:): * 当应用程序进入后台运行时调用,通常在此方法中保存应用程序的状态和数据 。 * 可以在此方法中执行一些后台任务和清理操作。 4. applicationWillEnterForeground(_:): * 当应用程序即将从后台切换到前台时调用,通常在此方法中恢复应用程序的状态和数据 。 * 可以在此方法中执行一些前台准备工作和更新界面操作。 5. applicationWillTerminate(_:): * 当应用程序即将终止时调用,通常在此方法中保存应用程序的最终状态和数据。 * 可以在此方法中执行一些清理操作和释放资源。

对应的通知的名称:

3.3 远程通知事件:

3.4 快捷操作QuickAction和用户活动UserActivity:

3.5 WatchKit交互

3.6 HealthKit交互

3.7 Opening a URL-specified resource

3.8 SiriKit事件处理

3.9 CloudKit事件处理

3.10 内存警告等系统环境变化回调

4. 应用级事件处理

官方介绍UIApplication

  • 应用程序生命周期管理:
    • shared:返回单例的UIApplication对象,用于获取应用程序的全局状态。
    • delegate:设置或获取应用程序的委托对象,通常为实现UIApplicationDelegate协议的对象。
  • 应用程序状态获取:
    • applicationState:获取当前应用程序的状态,包括活动状态、后台状态和挂起状态。 应用程序操作:
    • openURL(_:options:completionHandler:):通过指定的URL打开其他应用程序或执行其他操作。
    • canOpenURL(_:):检查是否可以通过指定的URL打开其他应用程序。
  • 通知注册:
    • registerForRemoteNotifications():注册接收远程通知。
    • unregisterForRemoteNotifications():取消接收远程通知的注册。
  • 本地通知:
    • presentLocalNotificationNow(_:):立即显示本地通知。
    • scheduleLocalNotification(_:):定时显示本地通知。
  • 应用程序图标角标:
    • applicationIconBadgeNumber:获取或设置应用程序图标的角标数字。 状态栏控制:
    • statusBarStyle:获取或设置状态栏的样式。
    • setStatusBarHidden(_:with:):显示或隐藏状态栏。
  • 应用程序退出:
    • terminate():退出应用程序。
  • 应用程序间跳转:
    • open(_:options:completionHandler:):通过指定的URL启动或跳转到其他应用程序。
    • open(_:options:completionHandler:):通过指定的URL启动或跳转到其他应用程序。
  • 系统声音与震动:
    • beginBackgroundTask(withName:expirationHandler:):开始后台任务,延长应用程序在后台运行的时间。
    • endBackgroundTask(_:):结束后台任务。
  • 远程控制:
    • beginReceivingRemoteControlEvents():开始接收远程控制事件。
    • endReceivingRemoteControlEvents():结束接收远程控制事件。
  • 应用程序状态保存和恢复:
    • beginBackgroundTask(withName:expirationHandler:):开始后台任务,延长应用程序在后台运行的时间。
    • endBackgroundTask(_:):结束后台任务。

5. 代码示例:

  • 设置应用程序图标右上角的红色提醒数字
    @property(nonatomic) NSInteger applicationIconBadgeNumber;

    objc 复制代码
        UIApplication *app = [UIApplication sharedApplication];
        app.applicationIconBadgeNumber = 10;
        // 创建通知对象
        UIUserNotificationSettings *setting = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeBadge categories:nil];
        // 注册用户通知
        [app registerUserNotificationSettings:setting];

    注:苹果为了增强用户体验,在iOS8以后我们需要创建通知才能实现图标右上角提醒,iOS8之前直接设置applicationIconBadgeNumber的值即可。

  • 设置联网指示器的可见性
    @property(nonatomic,getter=isNetworkActivityIndicatorVisible) BOOL networkActivityIndicatorVisible;

    objc 复制代码
    app.networkActivityIndicatorVisible= YES;
  • 管理状态栏

    从iOS7开始,系统提供了2种管理状态栏的方式

    a.通过UIViewController管理(每一个UIViewController都可以拥有自己不同的状态栏)在iOS7中,默认情况下,状态栏都是由UIViewController管理的,UIViewController实现下列方法就可以轻松管理状态栏的可见性和样式
    状态栏的样式   - (UIStatusBarStyle)preferredStatusBarStyle;
    状态栏的可见性  -(BOOL)prefersStatusBarHidden;

    objc 复制代码
    #pragma mark-设置状态栏的样式
    -(UIStatusBarStyle)preferredStatusBarStyle {
      //设置为白色
      //return UIStatusBarStyleLightContent;
      //默认为黑色
       return UIStatusBarStyleDefault;
    }
    #pragma mark-设置状态栏是否隐藏(否)
    -(BOOL)prefersStatusBarHidden {
      return NO;
    }

    b.通过UIApplication管理(一个应用程序的状态栏都由它统一管理)如果想利用UIApplication来管理状态栏,首先得修改Info.plist的设置,添加选中行,并将NO改为YES,这篇文章中有详细介绍iOS中用application 来管理电池栏状态

    Info.plist的设置:

    objc 复制代码
    //通过sharedApplication获取该程序的UIApplication对象
    UIApplication *app=[UIApplication sharedApplication];
    //设置状态栏的样式
    //app.statusBarStyle=UIStatusBarStyleDefault;//默认(黑色)
    //设置为白色+动画效果
    [app setStatusBarStyle:UIStatusBarStyleLightContent animated:YES];
    //设置状态栏是否隐藏
    app.statusBarHidden=YES;
    //设置状态栏是否隐藏+动画效果
    [app setStatusBarHidden:YES withAnimation:UIStatusBarAnimationFade];

    c.总结

    如果状态栏的样式只设置一次,那就用UIApplication来进行管理,并且UIApplication可以提供动画效果;

    如果状态栏是否隐藏,样式不一那就用每个控制器对自己的状态栏进行管理。

  • openURL:方法

    UIApplication有个功能十分强大的openURL:方法
    - (BOOL)openURL:(NSURL*)url;

    openURL:方法的部分功能有

    objc 复制代码
            UIApplication *app = [UIApplicationsharedApplication];
            打电话  [app openURL:[NSURLURLWithString:@"tel://110"]];
            发短信  [app openURL:[NSURLURLWithString:@"sms://10086"]];
            发邮件  [app openURL:[NSURLURLWithString:@"mailto://xxcc@fox.com"]];
            打开一个网页资源 [app openURL:[NSURL URLWithString:@"http://www.baidu.com"]];
            打开其他app程序   openURL方法,可以打开其他APP。

    系统内部根据不同的头标示来做出不同的相应。

  • 判断程序运行状态

    objc 复制代码
      //判断程序运行状态
      /*
       UIApplicationStateActive, 
       UIApplicationStateInactive, 
       UIApplicationStateBackground
       */
    UIApplication *app = [UIApplication sharedApplication];
    if(app.applicationState ==UIApplicationStateInactive){
          NSLog(@"程序在运行状态");
      }
  • 阻止屏幕变暗进入休眠状态

    objc 复制代码
     //阻止屏幕变暗,慎重使用本功能,因为非常耗电。
     UIApplication *app = [UIApplication sharedApplication];
     app.idleTimerDisabled =YES;
相关推荐
Code&Ocean35 分钟前
iOS从Matter的设备认证证书中获取VID和PID
ios·matter·chip
/**书香门第*/37 分钟前
Laya ios接入goole广告,开始接入 2
ios
恋猫de小郭16 小时前
什么?Flutter 可能会被 SwiftUI/ArkUI 化?全新的 Flutter Roadmap
flutter·ios·swiftui
网安墨雨20 小时前
iOS应用网络安全之HTTPS
web安全·ios·https
福大大架构师每日一题1 天前
37.1 prometheus管理接口源码讲解
ios·iphone·prometheus
BangRaJun2 天前
LNCollectionView-替换幂率流体
算法·ios·设计
刘小哈哈哈2 天前
iOS 多个输入框弹出键盘处理
macos·ios·cocoa
靴子学长2 天前
iOS + watchOS Tourism App(含源码可简单复现)
mysql·ios·swiftui
一如初夏丿2 天前
xcode15 报错 does not contain ‘libarclite‘
ios·xcode
杨武博3 天前
ios 混合开发应用白屏问题
ios