iOS – UI事件传递与响应者链

UI事件传递

UIView 内部包含一个 hitTest 方法,用于 UI 事件的传递。

当一个触摸事件发生时,事件传递顺序如下:

UIApplication -> UIWindow -> 寻找合适的View

假设颜色对应的 View 关系如下:

黄色:View 1
绿色:View 1_1
灰色:View 1_1_1
红色:View 1_2

View 1 定义如下:

class View1: UIView {

    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        print("View 1 hitTest")

        return super.hitTest(point, with: event)
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        print("View 1 touchesBegan")
    }

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        print("View 1 touchesMoved")
    }

    override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
        print("View 1 touchesCancelled")
    }
}

同理,View11View111View12 定义类似于 View1

View 的层级关系如下:

View1 -> View11 -> View111

View1 -> View12

具体布局代码如下:

// view 1
let view1 = View1(frame: CGRect(x: 40, y: 100, width: 300, height: 400))
view1.backgroundColor = UIColor.yellow
self.view.addSubview(view1)

// view 1_1
let view11 = View11(frame: CGRect(x: 20, y: 30, width: 100, height: 120))
view11.backgroundColor = UIColor.green
view1.addSubview(view11)

// view 1_1_1
let view111 = View111(frame: CGRect(x: 10, y: 10, width: 60, height: 40))
view111.backgroundColor = UIColor.gray
view11.addSubview(view111)

// view 1_2
let view12 = View12(frame: CGRect(x: 20, y: 200, width: 100, height: 120))
view12.backgroundColor = UIColor.red
view1.addSubview(view12)

当一个触摸事件发生在灰色(View 1_1_1)区域时,hitTest 被调用的顺序如下。

View 1 hitTest
View 1_2 hitTest
View 1_1 hitTest
View 1_1_1 hitTest

表明触摸事件发生时,先从底层 View 开始逐级向上层进行遍历,直到找到灰色视图 View111

响应者链

寻找到合适的 View 后,将会响应对应 View 的触摸事件。

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    print("View 1_1_1 touchesBegan")
}

此处合适的 View 就是 View111,将输出日志:

View 1_1_1 touchesBegan

响应者链的传递顺序与 UI 事件传递顺序正好相反,如果当前 View (即:View 1_1_1)无法响应触摸事件(比如:isUserInteractionEnabled = false),将向父一级传递事件。此时,View 1_1 将收到触摸事件,如果 View 1_1 可交互的话,将响应对应的 touchesBegan 事件。

View 1_1 touchesBegan

否则继续往父一级遍历,直到找到合适的响应对象。

示例代码

GitHub: iOS-Examples/Example-View响应链

Swift语法入门

本文简要介绍下 Swift 的基本语法。

  • 导入 package
import Foundation
  • 变量和常量
var str1 = "Hello, playground"
let str2 = "test"
  • 数组
var myArray = [
    "t1",
    "t2",
    "t3"
]
  • 集合
let ttt: Set = [0, 1, 2, 3, 6, 9, 90, 2]
  • 字典
var myDict = [
    "k1": "v1",
    "k2": "v2",
    "k3": "v3"
]
  • 方法
func test(_ value: Int) -> Bool {
    if value > 10 {
        return true
    }

    // 编译错误,Swift强制if语句加{}
    //    if value > 10
    //        return true

    return false
}
  • 多重返回值函数
func test1(_ value: Int) -> (errorCode: Int, errorDes: String) {
    if value > 10 {
        return (200, "Success")
    }

    return (404, "Not Found")
}

let r = test(100)
let r1 = test1(90)
print("result1 = (\(r1.0), \(r1.1))")
print("result1 = (\(r1.errorCode), \(r1.errorDes))")
  • 区间运算符
// 闭区间运算符(a...b)定义一个包含从a到b(包括a和b)的所有值的区间,b必须大于a。
for index in 1...5 {
    print("index = \(index)")
}
// 半开区间运算符
for index in 1..<5 {
    print("index = \(index)")
}
  • 闭包
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]

func backwards(s1: String, s2: String) -> Bool {
    return s1 > s2
}

func closureTest(_ param1: Int, param2: (_ s1: String, _ s2: String) -> Bool) {
    print("param1 = \(param1), param2 = \(param2("d", "b"))")
}

closureTest(1, param2: backwards)
closureTest(2, param2: {
    (s1: String, s2: String) -> Bool in

    return s1 < s2
})
closureTest(3, param2: {
    (s1, s2) in // 类型可自动推倒出来

    return s1 < s2
})
closureTest(4, param2: {(s1, s2) in return s1 < s2})
closureTest(5, param2: {$0 > $1})
// 运算符函数
closureTest(6, param2: >)
  • 尾随闭包
func trailingClosuresTest(_ param: () -> ()) {
    print("trailingClosuresTest 1")

    param()

    print("trailingClosuresTest 2")
}

// 以下是不使用尾随闭包进行函数调用
trailingClosuresTest({
    () -> () in

    print("trailingClosuresTest 3")
})
trailingClosuresTest({
    print("trailingClosuresTest 4")
})

// 以下是使用尾随闭包进行函数调用
trailingClosuresTest() {
    print("trailingClosuresTest 5")
}

// 如果函数只需要闭包表达式一个参数,当使用尾随闭包时,甚至可以把()省略掉。
trailingClosuresTest {
    print("trailingClosuresTest 6")
}
  • 捕获值
func makeIncrementor(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 1
    func incrementor() -> Int {
        runningTotal += amount
        return runningTotal
    }
    return incrementor
}

let capture1 = makeIncrementor(forIncrement: 9)
print(capture1())
print(capture1())

print 输出如下:

10
19

  • 枚举
enum Barcode {
    case UPCA(Int, Int, Int)
    case QRCode(String)
}

func enumTest(value: Barcode) {
    switch(value) {
    case Barcode.UPCA(let numberSystem, let identifier, let check):
        print("UPCA numberSystem = \(numberSystem), identifier = \(identifier), check = \(check)")
    case Barcode.QRCode(let productCode):
        print("QRCode productCode = \(productCode)")
    }
}

enumTest(Barcode.QRCode("qs"))
enumTest(Barcode.UPCA(1, 2, 3))

enum Planet: Int {
    case Mercury = 1, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune
}

enum StrEnum: String {
    case T1 = "a"
    case T2 = "b"
}
  • 计算属性
struct Point {
    var x = 0.0
    var y = 0.0
}
struct Size {
    var width = 0.0
    var height = 0.0
}
struct Rect {
    var origin = Point()
    var size = Size()
    var center: Point {
        get {
            let centerX = origin.x + (size.width / 2.0)
            let centerY = origin.y + (size.height / 2.0)

            return Point(x: centerX, y: centerY)
        }

        set {
            origin.x = newValue.x - (size.width / 2.0)
            origin.y = newValue.y - (size.height / 2.0)
        }
    }
}

var square = Rect(origin: Point(x: 0.0, y: 0.0), size: Size(width: 10.0, height: 10.0))
print("square.center = \(square.center)")
square.center = Point(x: 15.0, y: 15.0)
print("square origin = \(square.origin)")

print 输出如下:

square.center = Point(x: 5.0, y: 5.0)
square origin = Point(x: 10.0, y: 10.0)

  • 属性观察器
class StepCounter {
    var totalSteps: Int = 0 {

        willSet(newTotalSteps) {
            print("1 totalSteps = \(totalSteps)")
        }
        didSet {

            if(totalSteps > oldValue) {
                print("2 totalSteps = \(totalSteps)")
            }
        }
    }
}

let stepCounter = StepCounter()
stepCounter.totalSteps = 100

print 输出如下:

1 totalSteps = 0
2 totalSteps = 100

  • 结构体
struct PointTest {
    var x = 0.0
    var y = 0.0
    mutating func moveBy(deltaX: Double, deltaY: Double) {
        x += deltaX
        y += deltaY
    }
}

使用 mutating 关键字是为了能在该方法中修改 struct 的变量。

  • 构造、析构、! 、?
class Player {
    init() {
        print("Player init")
    }

    deinit {
        print("Player deinit")
    }

    func test() {

    }
}

var player: Player? = Player()    // 调用构造方法

if(nil != player) {
    player!.test()
}
// 等价于上面这种情况
player?.test()

player = nil    // 调用析构方法
  • 继承 - 单继承,多实现
class A {

}

class B: A {

}

protocol IA {

}

protocol IB {

}

protocol IC {

}

class C: A, IA, IB, IC {

}
  • extension
extension Double {
    var km: Double { return self * 1_000.0 }
    var m : Double { return self }
    var cm: Double { return self / 100.0 }
    var mm: Double { return self / 1_000.0 }
    var ft: Double { return self / 3.28084 }
}

let t = 45.7.km
print(t)

print 输出如下:

45700.0

Vapor 4.0.0 Beta 2 发布

更新日志

  • Services has been refactored to be more type-safe. (#2098)

Services, Container, and Provider have been replaced by a pattern built on Swift extensions. This is best explained with examples.

Telling Vapor to use Leaf:

import Leaf
import Vapor

// beta.1
s.register(ViewRenderer.self) { c in
    return c.make(LeafRenderer.self)
}

// beta.2
app.views.use(.leaf)

Registering a factory service:

// beta.1
s.register(Foo.self) { c in
    return Foo(...)
}
app.make(Foo.self).bar()

// beta.2
extension Application {
    var foo: Foo { 
        return Foo(...)
    }
}
app.foo.bar()

Registering a singleton service:

// beta.1
s.register(Foo.self) { c in
    return Foo(...)
}
app.make(Foo.self).bar = 0
app.make(Foo.self).bar += 1
print(app.make(Foo.self).bar) // 1

// beta.2
extension Application {
    var foo: Foo { 
        if let existing = self.storage[FooKey.self] as? Foo {
            return existing
        } else {
            let new = Foo()
            self.storage[FooKey.self] = new
            return new
        }
    }

    private struct FooKey: StorageKey { 
        typealias Value = Foo
    }
}

app.foo.bar = 0
app.foo.bar += 1
print(app.foo.bar) // 1

This new pattern of extending Application also works with Request:

extension Application {
    var foo: Foo { ... }
}

extension Request {
    var bar: Bar { 
        return self.application.foo.bar(for: self)
    }
}
  • Validations has been refactored to yield better and more type-safe errors (#2071)

  • Authentication methods are now grouped under a new req.auth helper (#2111)

  • All authenticators now accept Request in their authenticate methods (#2111)

  • Added new ErrorSource struct to the AbortError protocol (#2093)

This new struct makes it easier to pass around information about where an error came from. It also makes it easier to indicate that a given error has no source information. This helps the logger avoid muddying logs with useless error source information.

  • RouteBuilder HTTP method helpers now support an array of [PathComponent] (#2097)

  • User-provided HTTP headers are no longer ignored when using XCTVapor test methods (#2108)

  • Enabled test discovery on Linux (#2118)

iOS – UIView和CALayer的关系

CALayer 主要负责显示内容,继承自 NSObject

UIView 主要对 CALayer 做了简单的封装(UIView 类中有个成员变量 layer 就是 CALayer 类型)。另外,UIView 继承自 UIResponder 类,所以也会负责处理触摸事件的响应。

  • UIView 部分源码如下
open class UIView : UIResponder, NSCoding, UIAppearance, UIAppearanceContainer, UIDynamicItem, UITraitEnvironment, UICoordinateSpace, UIFocusItem, UIFocusItemContainer, CALayerDelegate {

    open class var layerClass: AnyClass { get }

    public init(frame: CGRect)

    public init?(coder: NSCoder)

    open var isUserInteractionEnabled: Bool

    open var tag: Int

    open var layer: CALayer { get }

    @available(iOS 9.0, *)
    open var canBecomeFocused: Bool { get }

    @available(iOS 9.0, *)
    open var isFocused: Bool { get }

    @available(iOS 9.0, *)
    open var semanticContentAttribute: UISemanticContentAttribute

    ......
}

当对一个视图进行绘制的时候,绘图单元会向 CALayer 索取要显示元素的相关数据,此时, CALayer 会通过 delegate 通知到 UIView,看看 UIView 是否有提供需要绘制的元素。如果 UIView 什么都不需要提供的话,就当作无视。