# iOS基础 # UIKit学习整理

整理了UIKit中一些用的少却很重要的知识,用Swift语言

1、UIWindow

简介

1、UIWindow 继承自 UIView,是App启动后被创建的第一个视图控件,是最顶层的UI容器

2、UIWindow 为应用的界面提供容器以及事件传递

3、每一个view的展示都需要依赖window,程序间的window是相互独立的

4、除非一个A品牌可以显示在一个外部的设备屏幕,不然一个App同一时间只有一个KeyWindow

5、app中常见的window

AppDelegate中的 window :UIWindow

状态栏window :不在程序内持有

键盘window :UIRemoteKeyboardWindow、UITextEffectsWindow

属性方法

1、screen

该属性默认为[UIScreen mainScreen],一个UIScreen对象对应一个实际设备的物理屏幕,一般情况下,我们不需要对其进行设置。一个iPhone默认也就一个屏幕,一个屏幕可以存在多个window,那也是为什么我们一个程序里面可以有多个window的原因。

当一个iPhone连接一个外接屏幕的时候,系统会发送通知。然而如果我们什么都不做,外接屏幕会一片漆黑,因为在那个屏幕上不存在任何window对象。如果真的想要在外接的屏幕中显示一些东西的话,那就应该监听系统通知,在接收通知的方法里创建一个新的window,并将其显示,当然,断开连接的时候,应该将window对象置为nil释放。以下为官方示例代码:

- (void)handleScreenConnectNotification:(NSNotification*)aNotification {
    UIScreen*    newScreen = [aNotification object];
    CGRect        screenBounds = newScreen.bounds;
 
    if (!_secondWindow) {
        _secondWindow = [[UIWindow alloc] initWithFrame:screenBounds];
        _secondWindow.screen = newScreen;
 
        // Set the initial UI for the window and show it.
        [self.viewController displaySelectionInSecondaryWindow:_secondWindow];
        [_secondWindow makeKeyAndVisible];
    }
}
 
- (void)handleScreenDisconnectNotification:(NSNotification*)aNotification {
    if (_secondWindow) {
        // Hide and then delete the window.
        _secondWindow.hidden = YES;
        [_secondWindow release];
        _secondWindow = nil;
 
        // Update the main screen based on what is showing here.
        [self.viewController displaySelectionOnMainScreen];
    }
}

2、UIWindowLevel

即window在z轴上的层级关系 默认是 normal

extension UIWindow.Level {
    public static let normal: UIWindow.Level   //rawValue = 0
    public static let alert: UIWindow.Level   //rawValue = 2000
    public static let statusBar: UIWindow.Level   //rawValue = 1000
}


层级值越大,层级越高,显示在越上面

3、rootViewController

该属性为window的根控制器,现在这个属性是不能为空的,必须进行赋值,否则程序会崩溃。

4、func makeKeyAndVisible()

但是makeKeyAndVisible会将一个window设置为keyWindow并将其显示

5、func sendEvent(_ event: UIEvent)

有事件需要处理的时候UIApplication会调用该方法派发事件。

windows

应用程序中所有的window对象,包括正在显示的或隐藏的window。

UIApplication.shared.windows

keyWindow

keyWindow是指定的用来接收键盘以及非触摸类的消息事件的UIWindow,而且程序中每个时刻只能有一个UIWindow是keyWindow。(触摸事件是传递给触摸事件发生的window,不一定是keyWindow)

keyWindow 获取方式

UIApplication.shared.keyWindow

成为keywindow与windowLevel无关,并不是windowLevel最高的window会成为keywindow

UIWindow的使用

1、window的销毁

如果是keyWindow,先变成非keyWindow

再hide,再置为nil

2、坐标转换


3、通知

https://www.jianshu.com/p/a6ef2c855a17

2、UIApplication

简介

1、UIApplication对象是应用程序的象征,一个UIApplication对象就代表一个应用程序

2、每一个应用都有自己的UIApplication对象,而且是单例的,如果试图在程序中新建一个UIApplication对象,那么将报错提示

3、通过 UIApplication.shared 可以获得这个单例对象

4、一个iOS程序启动后创建的第一个对象就是UIApplication对象

5、利用UIApplication对象,能进行一些应用级别的操作

UIApplicationDelegate

class AppDelegate: UIResponder, UIApplicationDelegate 

默认为AppDelegate,在app受到干扰时,会产生一些系统事件,这时UIApplication会通知它的delegate对象,让delegate代理来处理这些系统事件。

//当应用程序启动完毕的时候就会调用(系统自动调用)
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) 

//即将失去活动状态的时候调用(失去焦点, 不可交互)
func applicationWillResignActive(_ application: UIApplication) 

//应用程序进入后台的时候调用, 一般在该方法中保存应用程序的数据, 以及状态
func applicationDidEnterBackground(_ application: UIApplication) 

//应用程序即将进入前台的时候调用, 一般在该方法中恢复应用程序的数据,以及状态
func applicationWillEnterForeground(_ application: UIApplication)

//重新获取焦点(能够和用户交互)
func applicationDidBecomeActive(_ application: UIApplication) 

//应用程序即将被销毁的时候会调用该方法, 注意:如果应用程序处于挂起状态的时候无法调用该方法
func applicationWillTerminate(_ application: UIApplication) 

main函数

UIApplicationMain(CommandLine.argc, UnsafeMutableRawPointer(CommandLine.unsafeArgv).bindMemory(to: UnsafeMutablePointer<Int8>.self, capacity: Int(CommandLine.argc)), NSStringFromClass(UIApplication.self), NSStringFromClass(AppDelegate.self))

1、argc 系统或者用户传入的参数个数

2、UnsafeMutablePointer<UnsafeMutablePointer<Int8>> 系统或者用户传入的实际参数

3、principalClassName  UIApplication名称,用来创建 UIApplication对象,默认UIApplication

4、delegateClassName  delegate名称,用来创建 UIApplicationDelegate对象,默认AppDelegate

5、构建运行循环

程序启动的完整过程


1、main函数

2、UIApplicationMain

* 构建UIApplication

* 构建UIApplication的delegate对象 (默认  AppDelegate)

3、delegate对象开始处理系统事件 

////////如果没有storyboard

* 程序启动完毕的时候, 就会调用代理的  application:didFinishLaunchingWithOptions:方法

* 在application:didFinishLaunchingWithOptions:中创建UIWindow

* 创建和设置UIWindow的rootVC

* 显示窗口

////////如果有storyboard

* 根据Info.plist 获得storyboard的文件名,加载storyboard

* 创建UIWindow

* 创建和设置UIWindow的rootVC

* 显示窗口



UIApplication可以完成的系统层的问题

1、设置通知小红点

就是AppIcon右上角的小红点

applicationIconBadgeNumber

2、设置联网指示器的可见性

状态栏上网络加载的小菊花

networkActivityIndicatorVisible

3、管理状态栏

1、通过 VC的 preferredStatusBarStyle 属性的方式


2、UIApplication.shared.statusBarStyle = .default

需要在Info.plist设置 View controller-based status bar appearance 为 NO


如果只是设置一次,建议用 UIApplication管理

如果VC变化比较多,通过控制器就好了

4、openURL

func open(_ url: URL, options: [UIApplication.OpenExternalURLOptionsKey : Any] = [:], completionHandler completion: ((Bool) -> Void)? = nil)

打电话:

发短信:

打开网页:

打开其他App:

3、事件响应相关

在iOS中不是任何对象都能处理事件,只有继承了UIResponder的对象才能接收并处理事件。我们称之为“响应者对象”,UIApplication、UIViewController、UIView都继承自UIResponder,因此它们都是响应者对象,都能够接收并处理事件。

1、事件分类

1、触摸事件: 通过触摸、手势进行触发(例如手指点击、缩放、旋转)



//一根或者多根手指开始触摸view,系统会自动调用view的下面方法
open func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)

//一根或者多根手指在view上移动,系统会自动调用view的下面方法(随着手指的移动,会持续调用该方法)
open func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?)

//一根或者多根手指离开view,系统会自动调用view的下面方法
open func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?)

//触摸结束前,某个系统事件(例如电话呼入)会打断触摸过程,系统会自动调用view的下面方法
open func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?)

2、运动事件:通过加速器进行触发(例如手机晃动)

3、远程控制事件:通过其他远程设备触发(例如耳机控制按钮)

2、运动事件:通过加速器进行触发(例如手机晃动)

//运动开始时执行
override func motionBegan(_ motion: UIEvent.EventSubtype, with event: UIEvent?)
//运动结束后执行
override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?)
//运动被意外取消时执行
override func motionCancelled(_ motion: UIEvent.EventSubtype, with event: UIEvent?) 

3、远程控制事件:通过其他远程设备触发(例如耳机控制按钮)

//接收到远程控制消息时执行
func remoteControlReceived(with event: UIEvent?)
image

事件我们需要理解两个对象: UITouch、UIEvent,响应者我们需要理解 UIResponder

1、UIEvent

1、iOS使用UIEvent表示用户交互的事件对象,在UIEvent中,我们可以看到有一个type: UIEventType类型的属性,这个属性表示了当前的响应事件类型。

2、一个 UIEvent 代表一个事件,它是比 UITouch 更为抽象的对象,相当于把 UITouch 或者其他行为包装了一下。 一个 UIEvent 可以包含一个 UITouch(单指触控)或者 多个 UITouch (多指触控)。也可以包含设备(如 iPhone )晃动、远程遥控(如通过耳机线调整音量)等行为。

3、在一个用户点击事件处理过程中,UIEvent对象是唯一的。

2、UITouch

1、一个 UITouch 对象, 就是一根手指触摸一次屏幕。 它包含了 “触摸屏幕 — 滑动 — 离开屏幕” 整个过程。 所以 UITouch 有个 phase: UITouchPhase 属性,记录了 整个过程的所有3个状态(began、moved、ended)。由于一个电话或者其他事件可能会突然中断用户的操作,所以 UITouch 还外有 1 个取消状态 (cancelled)。以及1个 stationary(少出现)。

2、一个 UITouch 每当进入一次新的状态,它的一些显而易见的属性都会随之变化。如,位置、前一个位置、时间戳(timestamp: NSTimeInterval ,简单来讲,时间戳记录了自从上次开机的时间间隔)。不过当一个 UITouch 表面上从一个 View 移动到另一个 View 上时,UITouch 的 view 和 window 属性也不会变化。换句话说,UITouch 一发生就和 UITouch 最开始发生的( initial )的 view 绑定了。

3、每次点击发生的时候,点击对象都放在一个集合中传入前面列的四个 UIResponder的回调方法中,我们通过集合中对象获取用户点击的位置。

func location(in view: UIView?) -> CGPoint 获取当前点击坐标点,
func previousLocation(in view: UIView?) -> CGPoint获取上个点击位置的坐标点。

4、如果一个 UITouch 紧接着上一个 UITouch 发生,只要满足两个 UITouch 在一定时间、一定范围的条件,那么第二个 UITouch 就不算一个完全独立的 UITouch。一个明显的属性是 tapcount,即点击次数,其实这里理解成第几次点击更为确切。tapCount 至少为 1,可以为 2,3,4… … 等等。他们分别意味着单击,双击,三连击等等动作。UITouch 有个 UIGestureRecognizers 数组,里面装了所有接受该 UITouch GR。如果没有 GR 接收,那么该数组为空。

3、UIResponder

在UIKit中,UIApplication、UIView、UIViewController这几个类都是直接继承自UIResponder类。因此UIKit中的视图、控件、视图控制器,以及我们自定义的视图及视图控制器都有响应事件的能力。这些对象通常被称为响应对象,或者是响应者(以下我们统一使用响应者)。

1、管理响应链

UIResponder提供了几个方法来管理响应链,包括让响应对象成为第一响应者、放弃第一响应者、检测是否是第一响应者以及传递事件到下一响应者的方法,我们分别来介绍一下。

上面提到在响应链中负责传递事件的方法是nextResponder,其声明如下:

1、open var next: UIResponder? { get }

UIResponder类并不自动保存或设置下一个响应者,该方法的默认实现是返回nil。子类的实现必须重写这个方法来设置下一响应者。UIView的实现是返回管理它的UIViewController对象(如果它有)或者其父视图。而UIViewController的实现是返回它的视图的父视图;UIWindow的实现是返回app对象 UIApplication;而UIApplication的实现是返回nil。所以,响应链是在构建视图层次结构时生成的。


2、open var isFirstResponder: Bool { get }

一个响应对象可以成为第一响应者,也可以放弃第一响应者。为此,UIResponder提供了一系列方法,我们分别来介绍一下。
如果想判定一个响应对象是否是第一响应者,则可以使用以下方法:


3、open func becomeFirstResponder() -> Bool

如果对象成为第一响应者,则返回YES;否则返回NO。默认实现是返回YES。子类可以重写这个方法来更新状态,或者来执行一些其它的行为。

一个响应对象只有在当前响应者能放弃第一响应者状态(canResignFirstResponder)且自身能成为第一响应者(canBecomeFirstResponder)时才会成为第一响应者。

这个方法相信大家用得比较多,特别是在希望UITextField获取焦点时。另外需要注意的是只有当视图是视图层次结构的一部分时才调用这个方法。如果视图的window属性不为空时,视图才在一个视图层次结构中;如果该属性为nil,则视图不在任何层次结构中。


4、open var canBecomeFirstResponder: Bool { get } // default is NO

上面提到一个响应对象成为第一响应者的一个前提是它可以成为第一响应者,我们可以使用canBecomeFirstResponder方法来检测

需要注意的是我们不能向一个不在视图层次结构中的视图发送这个消息,其结果是未定义的。
与上面两个方法相对应的是响应者放弃第一响应者的方法,其定义如下:

5、open func resignFirstResponder() -> Bool
resignFirstResponder默认也是返回YES。需要注意的是,如果子类要重写这个方法,则在我们的代码中必须调用super的实现。

6、open var canResignFirstResponder: Bool { get } // default is YES

canResignFirstResponder默认也是返回YES。不过有些情况下可能需要返回NO,如一个输入框在输入过程中可能需要让这个方法返回NO,以确保在编辑过程中能始终保证是第一响应者。

2、管理输入视图

所谓的输入视图,是指当对象为第一响应者时,显示另外一个视图用来处理当前对象的信息输入,如UITextView和UITextField两个对象,在其成为第一响应者是,会显示一个系统键盘,用来输入信息。这个系统键盘就是输入视图。输入视图有两种,一个是inputView,另一个是inputAccessoryView。

与inputView相关的属性有如下两个:

open var inputView: UIView? { get }
open var inputViewController: UIInputViewController? { get }


这两个属性提供一个视图(或视图控制器)用于替代为UITextField和UITextView弹出的系统键盘。我们可以在子类中将这两个属性重新定义为读写属性来设置这个属性。如果我们需要自己写一个键盘的,如为输入框定义一个用于输入身份证的键盘(只包含0-9和X),则可以使用这两个属性来获取这个键盘。


与inputView类似,inputAccessoryView也有两个相关的属性:

open var inputAccessoryView: UIView? { get }
open var inputAccessoryViewController: UIInputViewController? { get }

设置方法与前面相同,都是在子类中重新定义为可读写属性,以设置这个属性。
另外,UIResponder还提供了以下方法,在对象是第一响应者时更新输入和访问视图

open func reloadInputViews()

调用这个方法时,视图会立即被替换,即不会有动画之类的过渡。如果当前对象不是第一响应者,则该方法是无效的。

3、响应触摸事件、移动事件、远程控制事件

事件响应链

4、验证命令

在我们的应用中,经常会处理各种菜单命令,如文本输入框的”复制”、”粘贴”等。UIResponder为此提供了两个方法来支持此类操作。首先使用以下方法可以启动或禁用指定的命令:

open func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool

open func target(forAction action: Selector, withSender sender: Any?) -> Any?



例如:

class MyTextField: UITextField {

    //去掉了 UITextField 的copy菜单 UIMenuItem
    override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
        if action == #selector(copy(_:)) {
            return false
        }
        return super .canPerformAction(action, withSender: sender)
    }
    
    
    //用于响应具体菜单方法的执行
    override func target(forAction action: Selector, withSender sender: Any?) -> Any? {
        return super.target(forAction: action, withSender: sender)
    }
}

推荐阅读更多精彩内容

  • 重点参考链接: View Programming Guide for iOS https://developer....
    Kevin_Junbaozi阅读 1,514评论 0 15
  • 本文主要讲解iOS触摸事件的一系列机制,涉及的问题大致包括: 触摸事件由触屏生成后如何传递到当前应用? 应用接收触...
    baihualinxin阅读 408评论 0 8
  • 好奇触摸事件是如何从屏幕转移到APP内的?困惑于Cell怎么突然不能点击了?纠结于如何实现这个奇葩响应需求?亦或是...
    Lotheve阅读 25,599评论 44 394
  • 在iOS开发中经常会涉及到触摸事件。本想自己总结一下,但是遇到了这篇文章,感觉总结的已经很到位,特此转载。作者:L...
    WQ_UESTC阅读 2,517评论 0 12
  • 悠悠醒来的唐南揉了揉额头,‘嘶,真疼啊。’ 唐南感觉自己应该是被抓了,额,不对,是确实被抓了。虽然是被关在车里,左...
    南来一小妖阅读 25评论 0 0