第十八章——觸摸事件 和 UIResponder【譯】

在接下來的兩章中,您將創建 TouchTracker,該應用程序中用戶可以通過觸摸屏幕來畫畫。 在本章中,您將創建一個視圖來使用戶能通過拖動來畫線(圖18.1)。 使用多點觸控的話,用戶一次可以繪制多條線。

圖18.1 TouchTracker

觸摸事件

作為 UIResponder 的子類,UIView 可以覆蓋四種方法來處理四個不同的觸摸事件:

  • 一個或多個手指觸摸屏幕
    func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)

  • 一個或多個手指在屏幕上移動(該信息隨著手指的移動而重復發送)
    func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?)

  • 一個或多個手指從屏幕上移除
    func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?)

  • 系統事件(如來電)在觸摸結束之前中斷它
    func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?)

讓我們看一下典型的觸摸生命周期。 當用戶的手指觸摸屏幕時,會創建一個 UITouch 的實例。 在手指觸摸的 UIView 上調用 touchesBegan(_:with :) 方法,并通過一個 touches Set 傳入 UITouch

當手指圍繞屏幕移動時,觸摸對象被更新以包含手指在屏幕上的當前位置。 然后,最初觸摸的 UIView 將被發送消息 touchesMoved(_:with :)。 作為參數傳遞給此方法的 Set 中包含的 UITouch 就是之前手指觸摸屏幕時創建的。

當手指從屏幕上移除時,最后一次更新觸摸對象以包含手指的最終位置,并且最初觸摸的視圖被發送消息 touchesEnded(_:with :)。 該方法完成執行后,UITouch 對象將被銷毀。

從這些信息,您可以得出關于觸摸對象如何工作的一些結論:

  • 一個 UITouch 對應于屏幕上的一根手指。 只要手指在屏幕上,該觸摸對象就可以生存,并且始終包含手指在屏幕上的當前位置。
  • 手指最初觸摸的視圖將接收該手指的每個觸摸事件消息。 即使手指移動超出最初觸摸的 UIView 的邊界,在該視圖上仍然會調用 touchesMoved(_:with :)touchesEnded(_:with :) 方法。 因此,如果觸摸從一個視圖開始,則該視圖擁有觸摸的生命周期。
  • 您不必——也不應該——永遠不要——引用 UITouch 對象。 該應用程序將允許您通過觸摸生命周期中不同點調用的 UIResponder 方法訪問觸摸對象。

每次觸摸都會像開始,移動或結束一樣——觸摸事件(touch event) 被添加到 UIApplication 對象管理的事件的隊列中。 在實踐中,隊列很少會填滿,而且事件會立即發送。 這些觸摸事件的傳遞包括將一個 UIResponder 消息發送到擁有觸摸的視圖。

多點觸摸呢? 如果多個手指在同一視圖的同一時間完成相同的事情,則所有這些觸摸事件都將一次發送。 每個觸摸對象——每個手指一個——包含在作為 UIResponder 消息中的參數傳遞的 Set 中。 然而,同一時間內觸摸的位置都不相同。 因此,通常不是一個響應者消息對應所有觸摸,而是多個 響應者消息對應一個或多個觸摸。 本章稍后將介紹如何處理多點觸摸。

創建 TouchTracker 應用程序

現在讓我們開始你的應用程序。 在 Xcode 中,創建一個新的 single view universal project,并將其命名為 TouchTracker (圖18.2)。

圖18.2 創建TouchTracker

在構建 TouchTracker 時,您將使用模板創建的默認視圖控制器和 storyboard文件。 對于其視圖和模型圖層,您將要創建自定義視圖類和自定義結構體。 TouchTracker 的主要部分如圖18.3所示。

圖18.3 TouchTracker的對象圖

我們從你的自定義結構體開始。

創建 Line 結構體

您將創建自定義 Line 類型。 到目前為止,您創建的所有類型都是類。 其實他們一直是 Cocoa Touch 子類; 例如,您已經創建過 NSObjectUIViewControllerUIView 的子類。

Line 將是一個 結構體(struct)。 您在本書中使用了結構體-- CGRectCGSizeCGPoint 都是結構體。 StringIntArrayDictionary 也是如此。 現在你要創建一個你自定義的結構體。

創建一個名為 Line 的新的 Swift 文件。

Line.swift 中,導入 CoreGraphics 并聲明 Line 結構體。 聲明兩個 CGPoint 屬性,用于確定該線的起點和終點。

import Foundation
import CoreGraphics

struct Line {
??var begin = CGPoint.zero
??var end = CGPoint.zero
}

結構體

結構體與類有很多不同:

  • 結構體不支持繼承。
  • 如果沒有聲明其他構造器,Struct 將獲得 成員構造器(member-wise initializer)。 成員構造器接受類型中每個屬性的參數。 例如,Line struct 具有成員構造器 init(begin:CGPoint,end:CGPoint)
  • 如果所有屬性都具有默認值,并且沒有聲明其他初始值設置,那么結構體也將獲得一個空的構造器(init()),它創建一個實例并將所有屬性設置為其默認值。
  • 也許最重要的是,結構體(和枚舉)是 值類型(value type) —— 而不是類(它們是引用類型(reference type)) 。

值類型與引用類型

值類型是將值分配給另一個實例或傳遞給函數的參數時將其值復制的類型。 這意味著將值類型的實例分配給另一個實例會將第一個實例的副本分配給第二個實例。 值類型在 Swift 中起著重要作用。 例如,數組和字典都是值類型。 你寫的所有枚舉和結構體也是值類型。

當引用類型被分配給一個實例或傳遞給一個函數的參數時,它們不被復制。 而是傳遞對同一個實例的引用。 類和閉包是引用類型。

那么你該用哪個呢? 一般來說,我們建議您使用值類型(如結構體),除非您完全知道您使用引用類型會更有益。 值類型更容易理解,因為您不需要擔心在更改副本上的值時實例會發生什么。 如果您想對此主題進行更深入的討論,請查看 Swift Programming:The Big Nerd Ranch Guide

創建 DrawView

除了自定義結構體之外,TouchTracker 還需要自定義視圖。

創建一個名為 DrawView 的新 Swift 文件。 在 DrawView.swift 中,定義 DrawView 類。 添加兩個屬性:一個可選的 Line 來跟蹤可能被繪制的線,以及一個 Line 數組來跟蹤已經繪制的線。

import Foundation
import UIKit

class DrawView: UIView {

??var currentLine: Line?
??var finishedLines = [Line]()

}

DrawView 的一個實例將是應用程序的 rootViewController 的視圖,項目中已經存在一個 ViewController。 視圖控制器需要知道它的視圖將是 DrawView 的一個實例。

打開 Main.storyboard。 選擇 View 并打開身份檢查器(Command-Option-3)。 在 Custom Class 下,將 Class 更改為 DrawView(圖18.4)。

圖18.4 更改視圖類

使用 DrawView繪制

DrawView 的實例需要能夠繪制線。 您將編寫一種使用 UIBezierPath 根據給定 Line 的屬性創建和描繪路徑的方法。 然后,您將覆蓋 draw(_ :) 來繪制完成線數組中的線以及當前的線(如果有)。

DrawView.swift 中,實現用于畫線的方法,并覆蓋 draw(_ :)

var currentLine: Line?
var finishedLines = [Line]()

func stroke(_ line: Line) {
??let path = UIBezierPath()
??path.lineWidth = 10
??path.lineCapStyle = .round

??path.move(to: line.begin)
??path.addLine(to: line.end)
??path.stroke()
}

override func draw(_ rect: CGRect) {
??// Draw finished lines in black
??UIColor.black.setStroke()
??for line in finishedLines {
????stroke(line)
??}

??if let line = currentLine {
????// If there is a line currently being drawn, do it in red
????UIColor.red.setStroke()
????stroke(line)
??}

}

觸屏畫線

一條線由兩點定義。 您的 Line 將這些點存儲為名為 beginend 的屬性。 觸摸開始時,您將創建一個 Line,并將其屬性設置為觸摸開始點。 觸摸移動時,您將更新 Lineend。 當觸摸結束時,您將擁有完整的一條線。

DrawView.swift 中,實現 touchesBegan(_:with :) 創建一條新的線。

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
??let touch = touches.first!

??// Get location of the touch in view's coordinate system
??let location = touch.location(in: self)

??currentLine = Line(begin: location, end: location)

??setNeedsDisplay()
}

該代碼首先在視圖的坐標系統中找出觸摸的位置。 然后它調用 setNeedsDisplay() 方法,該方法標記在運行循環結束時重繪的視圖。

接下來,還可以在 DrawView.swift 中實現 touchesMoved(_:with :),以便更新 currentLineend 屬性。

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
??let touch = touches.first!
??let location = touch.location(in: self)

??currentLine?.end = location

??setNeedsDisplay()
}

最后,仍然在 DrawView.swift 中,更新 currentLine 的結束位置,并在觸摸結束時將其添加到 finishedLines 數組。

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
??if var line = currentLine {
????let touch = touches.first!
????let location = touch.location(in: self)
????line.end = location

????finishedLines.append(line)
??}
??currentLine = nil

??setNeedsDisplay()
}

構建并運行應用程序并在屏幕上繪制一些線。 在繪制時,線將以紅色顯示。 一旦完成,它們將以黑色顯示。

處理多點觸控

繪制線時,您可能已經注意到,屏幕上有多個手指畫線并沒有任何作用——也就是說,您一次只能畫一條線。 讓我們更新 DrawView,以便您可以用多個手指來繪制盡可能多的線條。

默認情況下,視圖一次只能接受一個觸摸。 如果一個手指已經觸發了 touchesBegan(_:with :) 但尚未完成, 也就是沒有觸發 touchesEnded(_:with :), 所有后續的觸摸被忽略。 在這種情況下,“忽略” 意味著在 DrawView 上將不會調用 touchesBegan(_:with :) 和任何其他 UIResponder 方法與額外的觸摸相關的方法。

Main.storyboard 中,選擇 Draw View 并打開屬性檢查器。 選中標有 Multiple Touch 的框(圖18.5),以使 DrawView 實例的 multiTouchesEnabled 屬性設置為 true

圖18.5 多點觸控啟用

現在,DrawView 將接受多點觸控,每次手指觸摸屏幕,移動或從屏幕上移除時,相應的 UIResponder 將在視圖中被調用。 但是,您現在有一個問題:您的 UIResponder 代碼原本假定一次只會觸發一個觸摸和繪制一條線。

例如,每個觸摸處理方法要求它接收的一組 touches 集合中的 first 元素。 在單觸摸視圖中,集合中只會有一個對象,所以要請求何對象都總是返回一個觸發事件的對象。 在多點觸摸視圖中,該集可以包含多個觸摸。 此外,DrawView 只有一個屬性(currentLine)關聯到正在進行的線。 顯然,您將需要保留當前屏幕上觸摸的線的數量。 雖然您可以創建更多的屬性,如 currentLine1currentLine2,但是管理哪個屬性對應于哪個觸摸將是一件麻煩事。

您只需要用包含一個字典的實例的 Line 來替換單個 Line 而不用添加更多的屬性。 在 DrawView.swift 中,添加一個新屬性來替換 currentLine

class DrawView: UIView {

??var currentLine: Line?
??var currentLines = [NSValue:Line]()

該線存儲在字典中的 key 將來自該線對應的 UITouch 對象。 隨著更多的觸摸事件發生,您可以使用相同的算法從觸發事件的 UITouch 導出 key,并使用它在字典中查找適當的 Line

現在,您需要更新 UIResponder 方法來添加當前正在繪制到此字典的行。 在 DrawView.swift 中,更新 touchesBegan(_:with :) 中的代碼。

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
??let touch = touches.first!

??// Get location of the touch in view's coordinate system
??let location = touch.location(in: self)

??currentLine = Line(begin: location, end: location)

??// Log statement to see the order of events
??print(#function)

??for touch in touches {
????let location = touch.location(in: self)

????let newLine = Line(begin: location, end: location)

????let key = NSValue(nonretainedObject: touch)
????currentLines[key] = newLine
??}

??setNeedsDisplay()
}

在此代碼中,您首先使用 #function 表達式打印出方法的名稱。 其次,您列舉了所有觸摸的開始,因為可以同時開始多個觸摸。 (通常,觸摸在不同的時間開始,并且觸摸在每個觸摸的 DrawView 上多次調用 Bone(_:with :)。但你必須做好準備,即使它不太可能發生)

接下來,請注意使用 NSValue(nonretainedObject :) 來導出存儲 Line 的 key。 此方法創建一個 NSValue 實例,該實例將持有與該線關聯的 UITouch 對象的地址。 因為 UITouch 在觸摸開始時被創建,在其整個生命周期內被更新,并且在觸摸結束時被破壞,所以通過每個觸摸事件處理方法該對象的地址將是不變的。 圖18.6顯示了新的事態。

圖18.6 多點觸控的 TouchTracker 的對象圖

你可能會想:為什么不使用 UITouch 本身作為 key? 為什么要通過創建 NSValue 來導出? UITouch 的文檔說,你不應該對 UITouch 對象有很強的引用。 內存管理的細節超出了本書的范圍,但是為了避免創建對觸控對象的強大引用,您可以使用其 init(nonretainedObject :) 構造器將 UITouch 的內存地址包裝在 NSValue 的實例中。 該方法的文檔說明:“如果要將一個對象添加到集合中但是不希望集合創建一個強大的引用,這個方法很有用,” 這正是你想要的。 因為相同的 UITouch 對象被重復用于整個觸控的生命周期(因此具有相同的內存地址),您可以使用相同的 UITouch 重新創建相同的 NSValue

接下來,在 DrawView.swift 中更新 touchesMoved(_:with :),以便它可以查找正確的線。

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
??let touch = touches.first!
??let location = touch.location(in: self)

??currentLine?.end = location

??// Log statement to see the order of events
??print(#function)
??for touch in touches {
????let key = NSValue(nonretainedObject: touch)
????currentLines[key]?.end = touch.location(in: self)
??}

??setNeedsDisplay()
}

現在,更新 touchesEnded(_:with :) 將任何完成的線添加到 finishedLines 數組。

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
??if var line = currentLine {
????let touch = touches.first!
????let location = touch.location(in: self)
????line.end = location

????finishedLines.append(line)
??}
??currentLine = nil

??// Log statement to see the order of events
??print(#function)

??for touch in touches {
????let key = NSValue(nonretainedObject: touch)
????if var line = currentLines[key] {
??????line.end = touch.location(in: self)
??????finishedLines.append(line)
??????currentLines.removeValue(forKey: key)
????}
??}

??setNeedsDisplay()
}

最后,更新 draw(_ :) 來繪制 currentLines 中的每一條線。

override func draw(_ rect: CGRect) {
??// Draw finished lines in black
??UIColor.black.setStroke()
??for line in finishedLines {
????stroke(line)
??}

??if let line = currentLine {
????If there is a line currently being drawn, do it in red
????UIColor.red.setStroke()
????stroke(line)
??}

??// Draw current lines in red
??UIColor.red.setStroke()
??for (_, line) in currentLines {
????stroke(line)
??}
}

構建并運行應用程序,并用多個手指開始繪制線。 (在模擬器上,按住 Option(alt) 鍵的同時拖動以模擬多根手指。)請注意控制臺中日志消息的順序。

您應該知道,當在視圖上調用諸如 touchesMoved(_:with :) 之類的 UIResponder 方法時,只有已移動的觸摸將位于 touches 中。 因此,三個觸摸可以在一個視圖上,但是在該組觸摸中只有一個觸摸被傳遞到這些方法之一中。 另外,一旦 UITouch 從視圖開始,即使該觸摸從其開始的視圖移開,所有觸摸事件方法都會在觸摸的生命周期中同一視圖中被調用。

TouchTracker 的基礎知識的最后一件事是處理取消觸摸時會發生什么。 當應用程序被操作系統中斷時(例如,當電話進入時),當觸摸當前在屏幕上時,可以取消觸摸。 當觸摸被取消時,其設置的任何狀態都應該被恢復。 在這種情況下,您應該刪除正在繪制的任何線。

DrawView.swift 中,實現 touchesCancelled(_:with :)

override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
??// Log statement to see the order of events
??print(#function)

??currentLines.removeAll()

??setNeedsDisplay()
}

@IBInspectable

Interface Builder 中工作時,您可以修改添加到畫布的視圖的屬性。 例如,您可以在視圖上設置背景顏色,標簽上的文本以及滑塊上的當前進度。 您可以將相同的行為添加到您自己的某些類型的 UIView 子類。 我們可以通過 Interface Builder 增加 DrawView 當前線條顏色,最終線條顏色和線條厚度的功能。

DrawView.swift 中,聲明三個屬性來引用這些值。 給它們默認值,并且只要這些屬性改變,都有自己的 view 標志來重新繪制。

var currentLines = [NSValue:Line]()
var finishedLines = [Line]()

@IBInspectable var finishedLineColor: UIColor = UIColor.black {
??didSet {
????setNeedsDisplay()
??}
}

@IBInspectable var currentLineColor: UIColor = UIColor.red {
??didSet {
????setNeedsDisplay()
??}
}

@IBInspectable var lineThickness: CGFloat = 10 {
??didSet {
????setNeedsDisplay()
??}
}

@IBInspectable 關鍵字讓 Interface Builder 知道這是您要通過屬性檢查器進行自定義的屬性。 許多常見類型由 @IBInspectable 支持:布爾值,字符串,數字,CGPointCGSizeCGRectUIColorUIImage 等等。

現在更新 stroke(_ :)drawView(_ :) 來使用這些新屬性。

func stroke(_ line: Line) {
??let path = UIBezierPath()
??path.lineWidth = 10
??path.lineWidth = lineThickness
??path.lineCapStyle = .round

??path.move(to: line.begin)
??path.addLine(to: line.end)
??path.stroke()
}

override func draw(_ rect: CGRect) {
??// Draw finished lines in black
??UIColor.black.setStroke()
??finishedLineColor.setStroke()
??for line in finishedLines {
????stroke(line)
??}

??// Draw current lines in red
??UIColor.red.setStroke()
??currentLineColor.setStroke()
??for (_, line) in currentLines {
????stroke(line)
??}
}

當您在 Interface Builder 中將 DrawView 添加到畫布中時,可以在屬性檢查器中自定義這三個屬性來改變實例(圖18.7)。

圖18.7 自定義DrawView

白銀挑戰:顏色

一旦線被添加到 currentLines,按畫線的角度來決定它的顏色。

黃金挑戰:圓圈

用兩根手指畫圓。 嘗試讓每個手指代表圍繞圓的邊框的一個角。 回想一下,您可以通過按住 Option 鍵在模擬器上模擬兩根手指。 (提示:如果您在單獨的字典中跟蹤在一個圓圈上工作的觸摸,這會更容易)

更多:響應者鏈

在第14章中,我們簡要介紹了第一響應者。 UIResponder 可以是第一響應者并且接收觸摸事件。 UIViewUIResponder 子類的一個例子,但還有很多其他的,包括 UIViewControllerUIApplicationUIWindow。 你可能在想,“你不能觸摸 UIViewController。 它不是一個屏幕上的對象。” 你是對的——你不能直接向 UIViewController 發送觸摸事件,但視圖控制器可以通過 響應者鏈(responder chain) 接收事件。

每個 UIResponder 可以通過其 next 屬性來引用另一個 UIResponder,并且這些對象組成響應者鏈(圖18.8)。 觸摸事件從被觸動的視圖開始。 視圖的 next 響應者通常是它的 UIViewController(如果它有)或它的父視圖(如果沒有)。 視圖控制器的 next 響應者通常是其視圖的父視圖。 最頂級的父視圖是窗口。 窗口的 next 響應者是 UIApplication 的單例實例。

圖18.8 響應者鏈

UIResponder 如何處理一個事件? 它在 next 響應者上調用相同的方法。 那就像 touchesBegan(_:with :) 這樣的方法的默認實現。 所以如果一個方法不被覆蓋,其 next 響應者將嘗試處理觸摸事件。 如果應用程序(響應者鏈中的最后一個對象)不處理該事件,則它將被丟棄。

您也可以在 next 響應者上顯式調用方法。 假如在一個視圖中,發生雙擊事件,它的 next 響應者應該處理它。 代碼將會如下所示:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
??let touch = touches.first!
??if touch.tapCount == 2 {
????next?.touchesBegan(touches, with: event)
??} else {
????// Go on to handle touches that are not double-taps
??}
}

更多:UIControl

UIControl 類是 Cocoa Touch 中很多類的父類,包括 UIButtonUISlider。 您已經了解了如何設置這些控件的 目標 和 動作。 現在我們可以仔細看看 UIControl 如何覆蓋在本章中實現的相同的 UIResponder 方法。

UIControl 中,每個可能的 控制事件(control event) 與一個常數相關聯。 例如,按鈕通常會在 UIControlEvents.touchUpInside 控件事件上發送動作消息。 注冊該控制事件的目標只有在用戶觸摸控制然后將手指從控件邊框內的屏幕提起時才會收到其動作消息。

但是,對于一個按鈕,您還可以對其他事件類型執行動作。 例如,如果用戶在邊框內部或外部移除手指,也可以觸發方法。 以編程方式分配目標和動作將如下所示:

button.addTarget(self, action: #selector(Thermostat.resetTemperature(_:)), for: [.touchUpInside, .touchUpOutside])

現在考慮 UIControl 如何處理 UIControlEvents.touchUpInside

// Not the exact code. There is a bit more going on!
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {

??// Reference to the touch that is ending
??let touch = touches.first!

??// Location of that point in this control's coordinate system
??let touchLocation = touch.location(in: self)

??// Is that point still in my viewing bounds?
??if bounds.contains(touchLocation) {
????// Send out action messages to all targets registered for this event!
????sendActions(for: .touchUpInside)
??}
??else {
????// The touch ended outside the bounds: different control event
????sendActions(for: .touchUpOutside)
??}
}

那么這些動作如何發送到正確的目標呢? 在 UIResponder 方法實現的末尾,控件調用了 sendActions(for :) 方法。 該方法查看控件具有的所有目標動作對。 如果其中任何一個注冊為作為參數傳遞的控制事件,則對這些目標調用相應的動作方法。

然而,控件不直接在其目標上調用方法。 而是通過 UIApplication 對象來引導這些方法調用。 為什么控件不直接在目標上調用動作方法呢? 控件也可以具有 空目標(nil-targeted) 的動作。 如果 UIControl 的目標為 nil,則 UIApplication 會找到其 UIWindow 的 第一響應者,并調用該方法。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,156評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,401評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,069評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,873評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,635評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,128評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,203評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,365評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,881評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,733評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,935評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,475評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,172評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,582評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,821評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,595評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,908評論 2 372

推薦閱讀更多精彩內容

  • 這篇文章是我在閱讀相關蘋果官方文檔后總結整理出來的一些平常可能不太注意到,但是又比較有用的知識點。如有錯誤,歡迎指...
    我們是斗士閱讀 1,163評論 0 0
  • 本文來自:http://ios.jobbole.com/84081/ 前言: 按照時間順序,事件的生命周期是這樣的...
    HackerOnce閱讀 2,855評論 1 10
  • 好奇觸摸事件是如何從屏幕轉移到APP內的?困惑于Cell怎么突然不能點擊了?糾結于如何實現這個奇葩響應需求?亦或是...
    Lotheve閱讀 57,622評論 51 601
  • 在iOS開發中經常會涉及到觸摸事件。本想自己總結一下,但是遇到了這篇文章,感覺總結的已經很到位,特此轉載。作者:L...
    WQ_UESTC閱讀 6,053評論 4 26
  • 響應者對象 在iOS中不是任何對象都能處理事件,只有繼承了UIResponder的對象才能接收并處理事件。我們稱之...
    JonesCxy閱讀 708評論 0 0