原文鏈接: UIGestureRecognizer教程:創(chuàng)建自定義手勢
學(xué)習(xí)如何使用自定義 UIGestureRecognizer
來識別圓
自定義手勢可以使app感覺更獨特,更有活力,從而取悅用戶。如果把基本的點擊、拖移和旋轉(zhuǎn)手勢比作iOS世界里的通用皮卡,自定義手勢則是擁有個性噴漆和水動力,且閃閃發(fā)光的hot rods(一種改裝車)。通過這篇自定義 UIGestureRecognizer
教程你可以了解所有關(guān)于手勢識別的知識!
在這篇教程中我們準(zhǔn)備了一個有趣的“找茬”小游戲,并通過使用自定義圓形手勢來選擇不一致圖片的方式進(jìn)行互動。在這個過程中你會學(xué)到如下幾點:
UIGestureRecognizer
子類所提供的狀態(tài)和回調(diào)機(jī)制來簡化手勢檢測。注意:本教程假定你已經(jīng)知道手勢識別是如何工作的,且知道如何在app中使用系統(tǒng)定義手勢。如果想速成,請看本站的UIGestureRecognizer教程。
MatchItUp 向用戶展示4張圖片,3張是一致的,另外1張和其他的略有不同。用戶的任務(wù)就是通過用手指在上面畫一個圓圈的方式找出不同的那張:
MatchItUp! 游戲
在這里下載教程初始項目
構(gòu)建并運(yùn)行app;你會看到4張圖片,但是你還不能選出不同的那張。你的任務(wù)就是給這個游戲添加一個自定義手勢識別器。當(dāng)用戶在一個圖片周圍畫了一個圓,自定義手勢識別器就會檢測到。如果他們剛好在不同的那張上畫了一個圓,就贏了!
打開File\New\File… 然后選擇 iOS\Source\Cocoa Touch Class模板來創(chuàng)建一個名為 CircleGestureRecognizer的UIGestureRecognizer子類。注意選擇Swift 。然后點擊Next然后再點擊Create。
為了讓手勢識別器生效,它必須連接到響應(yīng)鏈中的某個視圖。當(dāng)用戶點擊屏幕時,觸摸事件在視圖堆棧中轉(zhuǎn)發(fā),每個視圖上的手勢識別器都可以處理這些觸摸事件。打開GameViewController.swift然后為手勢識別器添加一個實例變量:
var circleRecognizer: CircleGestureRecognizer!
下一步,在 viewDidLoad
中添加如下代碼:
circleRecognizer = CircleGestureRecognizer(target: self, action: "circled:")
view.addGestureRecognizer(circleRecognizer)
這段代碼創(chuàng)建手勢識別器并將它加到主視圖上。
等等… 如果目標(biāo)是讓用戶圈出不同的那張圖片,為什么不直接把識別器添加每張圖片上,而是添加到主視圖呢?
好問題——很高興能對其進(jìn)行解答!:]
當(dāng)構(gòu)建一個手勢時,一定要對用戶界面的不精確性進(jìn)行補(bǔ)償。如果你曾經(jīng)嘗試過在觸摸屏上很小的一個框內(nèi)簽上你的名字,你就會明白我的意思!:]
當(dāng)把識別器放在整個view上時,它可以讓用戶在圖片的框之外更輕松滴開始和繼續(xù)某個手勢,最終,你的識別器也會減輕那些不能畫出完美圓用戶的壓力。
構(gòu)建然后運(yùn)行app;盡管你已經(jīng)創(chuàng)建了一個 UIGestureRecognizer
子類。但是你還沒有添加任何代碼所以很顯然它將只能識別…0個手勢!為了使其生效,需要為手勢識別器實現(xiàn)一個手勢識別狀態(tài)機(jī)。
所有用戶操作中最簡單的是點擊;用戶放下手指然后抬起。對于這個時間手勢識別器會調(diào)用兩個方法: touchesBegan(_:withEvent:)
和 touchesEnded(_:withEvent:)
。
在簡單的點擊手勢中,這兩個方法和手勢識別器的兩個狀態(tài) .Began
和 .Ended
對應(yīng):
為了看到這個動作的效果,你需要在 CircleGestureRecognizer
類中實現(xiàn)這個狀態(tài)機(jī)。
最先!在 CircleGestureRecognizer.swift
的最上面添加如下的 import
。
import UIKit.UIGestureRecognizerSubclass
UIGestureRecognizerSubclass
是 UIKit
中的一個公共頭文件,但是沒有包含在 UIKit
頭文件中。因為你需要更新 state
屬性,所以導(dǎo)入這個頭文件是必須的,否則, 它就只是 UIGestureRecognizer
中的一個只讀屬性。
現(xiàn)在在同一個類中添加如下代碼:
override func touchesBegan(touches: Set<NSObject>!, withEvent event: UIEvent!) {
super.touchesBegan(touches, withEvent: event)
state = .Began
}
override func touchesEnded(touches: Set<NSObject>!, withEvent event: UIEvent!) {
super.touchesEnded(touches, withEvent: event)
state = .Ended
}
如果此時運(yùn)行app然后點擊屏幕,app會因為你沒有對這個手勢進(jìn)行處理而崩潰。
為GameViewController.swift文件中的類添加如下代碼:
func circled(c: CircleGestureRecognizer) {
if c.state == .Ended {
let center = c.locationInView(view)
findCircledView(center)
}
}
當(dāng)手勢識別器的狀態(tài)改變時它的 target-action
就會被觸發(fā)。當(dāng)手指觸碰到屏幕時, touchesBegan(_:withEvent)
觸發(fā)。手勢識別器將其狀態(tài)置為 .Began
,然后自動調(diào)用 target-action
。當(dāng)手指離開屏幕時, touchesEnded(_:withEvent)
將其狀態(tài)置為 .Ended
,然后再次調(diào)用 target-action
。
早前在對手勢識別器進(jìn)行設(shè)置時,你已經(jīng)把 circled(_:)
方法指定為它的 target-action
。方法的實現(xiàn)中使用提供的 findCircledView(_:)
方法來檢測點擊的是哪一張圖片。
構(gòu)建并運(yùn)行app;點擊某張圖片然后選中它。游戲檢查你的選擇是否正確然后進(jìn)入下一輪:
點擊選擇某個圖片
現(xiàn)在你已經(jīng)有一個可用的點擊手勢識別器,對嗎?不要這么著急下結(jié)論,手指可多著呢!:] 注意方法的名字中包含的是 “touches”——復(fù)數(shù)。手勢識別器可以檢測多手指手勢,但是游戲的圓形手勢意味著我們只識別單手指手勢。
注:圖片中的意思是我也差點忘了…
你需要檢查是否只有一個手指觸摸了屏幕。
打開CircleGestureRecognizer.swift文件然后修改 touchesBegan(_:)
使 touches
參數(shù)只允許包含一個 UITouch
對象:
override func touchesBegan(touches: Set<NSObject>!, withEvent event: UIEvent!) {
super.touchesBegan(touches, withEvent: event)
if touches.count != 1 {
state = .Failed
}
state = .Began
}
這里要想你介紹手勢識別器的第三種狀態(tài):.Failed
。.Ended
表示手勢成功完成,而.Failed
表示用戶的手勢不是你想要的。
即時把狀態(tài)機(jī)置為終止?fàn)顟B(tài)是非常重要的,比如.Failed
,這樣其他等待響應(yīng)的手勢識別器才有機(jī)會解讀觸摸事件。
再次構(gòu)建并運(yùn)行app;嘗試多手指點擊和單手指點擊。這次只有單手指點擊才能對圖片進(jìn)行選擇。
“等等,”你喊道。“點擊跟圓根本不是一回事?。 ?/p>
當(dāng)然,如果你想了解其全部技術(shù)細(xì)節(jié)的話,一個點就是一個半徑為0的圓。但在這里并沒有什么意義;用戶必須圈住圖片,選擇才有效。
如果要檢測圓,你需要收集用戶移動手指時所經(jīng)過的點,然后看它們是否組成了一個圓。
看起來集合可以非常完美滴勝任這個工作。
在CircleGestureRecognizer
類的頂部添加如下的實例變量:
private var touchedPoints = [CGPoint]() // 記錄歷史點
用它來記錄用戶觸摸的點。
現(xiàn)在在CircleGestureRecognizer
類中添加如下方法:
override func touchesMoved(touches: Set<NSObject>!, withEvent event: UIEvent!) {
super.touchesMoved(touches, withEvent: event)
// 1
if state == .Failed {
return
}
// 2
let window = view?.window
if let touches = touches as? Set<UITouch>, loc = touches.first?.locationInView(window) {
// 3
touchedPoints.append(loc)
// 4
state = .Changed
}
}
在初始的觸摸事件之后用戶每移動手指都會觸發(fā) touchesMoved(_:withEvent:)
。每觸發(fā)一次就順序執(zhí)行用數(shù)字標(biāo)記的代碼塊:
.Changed
。這會有調(diào)用 target-action
的副作用。
.Changed
是另外一個添加到狀態(tài)機(jī)的狀態(tài)。每當(dāng)手勢識別器的觸摸事件發(fā)生變化時都會轉(zhuǎn)換到.Changed
狀態(tài);這些變化包括:手指移動、按下和抬起。
下面是添加了.Changed
狀態(tài)的狀態(tài)機(jī):
現(xiàn)在你已經(jīng)獲得了所有的點,如何去確定這些點是否組成一個圓呢?
首先,把如下的變量添加到 CircleGestureRecognizer.swift中類的頂部:
var fitResult = CircleResult() // 有關(guān)于這條路徑有多像圓的一些信息
var tolerance: CGFloat = 0.2 // 圓的容錯值
var isCircle = false
這些變量會幫助你決定在可容忍范圍內(nèi)這些店是否組成一個圓。
修改 touchesEnded(_:withEvent:)
讓它變成下面的樣子:
override func touchesEnded(touches: Set<NSObject>!, withEvent event: UIEvent!) {
super.touchesEnded(touches, withEvent: event)
// 現(xiàn)在用戶已經(jīng)完成了觸控,確定這條路徑是否是一個圓
fitResult = fitCircle(touchedPoints)
isCircle = fitResult.error <= tolerance
state = isCircle ? .Ended : .Failed
}
這里我們稍微作了點弊,使用了一個事先做好的圓形檢測器。你可以看一看CircleFit.swift,但我只會對它的內(nèi)部細(xì)節(jié)做一點點描述。檢測器的主要目的是將記錄的點擬合成一個圓。error
值代表了目前的路徑和真正的圓形偏離了多少,而tolerance
的存在則是因為你不能期望用戶能畫出一個完美的圓。如果error
的值在tolerance
的范圍內(nèi),手勢識別器將狀態(tài)置為.Ended
;否則將狀態(tài)置為.Failed
。
此時如果馬上構(gòu)建并運(yùn)行app,游戲不會正常工作,因為此時手勢識別器仍然按點擊來識別手勢。
回到GameViewController.swift,然后將circled(_:)
修改成如下的樣子:
這func circled(c: CircleGestureRecognizer) {
if c.state == .Ended {
findCircledView(c.fitResult.center)
}
}
這里使用計算得到的圓的中心點來確定用戶在哪個視圖畫圈了,而不是使用最后觸摸的那個點。
構(gòu)建并運(yùn)行app;嘗試使用你的手玩這個游戲——相當(dāng)蛋疼。讓app識別出你畫的圓不是那么簡單是不是?剩下的就是如何在數(shù)學(xué)理論和不精確的現(xiàn)實世界之間搭建一座橋梁的問題了。
因為在畫圓的過程中很難準(zhǔn)確地知道接下來該怎么畫,所以你需要把用戶手指移動的路徑繪制出來。iOS已經(jīng)在Core Graphics
中包含了你需要的功能。
把下面的實例變量聲明添加到CircleGestureRecognizer.swift中
var path = CGPathCreateMutable() // 運(yùn)行 CGPath - 輔助繪制
這個變量提供了一個可變的CGPath
對象,用于繪制路徑。
把下面的代碼添加到touchesBegan(_:withEvent:)
的底部:
let window = view?.window
if let touches = touches as? Set<UITouch>, loc = touches.first?.locationInView(window) {
CGPathMoveToPoint(path, nil, loc.x, loc.y) // 開始構(gòu)建路徑
}
這段代碼保證路徑從觸摸開始的位置開始。
現(xiàn)在把下面的代碼添加到touchesMoved(_:withEvent:)
底部if let
代碼塊的touchedPoints.append(loc)
下:
CGPathAddLineToPoint(path, nil, loc.x, loc.y)
每當(dāng)手指移動時,你通過畫線的方式把新的點添加到路徑中。不要擔(dān)心直線的部分;因為點和點之間距離很近,所以在你畫的路徑最終看起來會相當(dāng)流暢。
為了使路徑可見,需要將它繪制到游戲視圖中。在CircleDrawView
視圖層級中已經(jīng)有這樣一個視圖了。
如果要將路徑展示在這個視圖中,需要把下面的代碼添加到GameViewController.swift中circled(_:)
方法的底部。
if c.state == .Began {
circlerDrawer.clear()
}
if c.state == .Changed {
circlerDrawer.updatePath(c.path)
}
這段代碼當(dāng)新的手勢開始時會清除視圖中的內(nèi)容,然后跟蹤用戶的手指,使用黃色的線畫出路徑。
構(gòu)建并運(yùn)行app;嘗試在屏幕上畫圓然后觀察它是如何工作的:
酷斃了!但你是否有注意到當(dāng)畫第二個或者第三個圓時出現(xiàn)的搞笑情況呢?
盡管在變成.Began
狀態(tài)時有調(diào)用circlerDrawer.clear()
,但是每次做一個新手勢時,之前的并沒有被清除掉。這只能意味著:是時候為你手勢識別器的狀態(tài)機(jī)引入新的動作:reset()
了。
你需要在touchesEnded
之后 touchesBegan
之前調(diào)用 reset()
。這可以讓手勢識別器清除它的狀態(tài)然后重新開始。
把下面的方法添加到CircleGestureRecognizer.swift中:
override func reset() {
super.reset()
touchedPoints.removeAll(keepCapacity: true)
path = CGPathCreateMutable()
isCircle = false
state = .Possible
}
在這個方法里你清空了觸摸點集合,然后把 path
設(shè)置為新的路徑。同時,你把狀態(tài)設(shè)置為 .Possible
,這個狀態(tài)表示觸摸事件沒有被匹配到或者手勢失效了。
新的狀態(tài)機(jī)看起來會像下面這樣:
再次構(gòu)建并運(yùn)行app;這一次,每次觸摸時視圖內(nèi)容(以及手勢識別器的狀態(tài))都會被清除。
在 CircleFit
內(nèi)部到底發(fā)生了什么?然后為什么有的時候會把一些類似C、S的奇怪形狀當(dāng)作圓形?
僅僅是一條很短的線就被當(dāng)作一個圓
大家應(yīng)該記得在高中的時候就學(xué)過圓的方程:sqrt{x^2+y^2} = r^2
。如果用戶畫了個圓,那么所有的觸摸點都應(yīng)該完全符合這個方程式:
或者更準(zhǔn)確滴說,因為識別器需要識別出所有的圓,而不是以起始點為中心的圓,方程就應(yīng)該是 sqrt{(x-x_c)^2+(y-y_c)^2} = r^2
。當(dāng)手勢結(jié)束時,你所擁有的只是一堆僅僅有x和y的點集合。剩下的就是確定中心點 (x_c, y_c)
以及半徑 (r)
:
以xc,yc為中心的圓
確定中心點和半徑的方法有好幾種,本教程采用的方法改編自Nikolai Chernov用C++實現(xiàn)的Taubin擬合方法。流程如下:
首先,同時計算所有點的平均值來猜測圓的質(zhì)心(也就是所有點的x和y坐標(biāo))。如果是標(biāo)準(zhǔn)的圓,所有點的質(zhì)心就會是圓的圓心。如果這些點沒有組成標(biāo)準(zhǔn)的圓,那么計算出來的圓心就會有所偏離:
*圓心的猜測從一開始就是以所有點為基礎(chǔ)的*
sqrt{(x-x_c)^2+(y-y_c)^2} = r^2
方程式中x_c
、y_c
和r
的值都相同數(shù)學(xué)方法。最后,你計算出一個均方根誤差來做擬合。這是用來衡量實際的點和圓軌跡偏離多少的方法:
藍(lán)杠表示誤差,或者和紅色擬合圓和點的差距
所以他們說數(shù)學(xué)很難!哼!
腦袋疼么?其實這個又臭又長的算法只是在所有點的中心擬合一個圓,然后根據(jù)每個點和計算得到的圓心的距離得到半徑。然后再計算每個點和計算得到的圓之間的誤差值。如果誤差很小,就假定用戶畫了一個圓。
但是這個算法在路徑組成對稱圓形,比如C和S這種計算得到的誤差值很小的情況,或者路徑組成很短的弧或線,而這些點被當(dāng)作一個大得多的圓上的一小段的情況時就會出錯。
大部分的點都在圓上,其他的點足夠?qū)ΨQ從而使它們能“互相抵消”
這張圖展示了一條線是如何被擬合成一個圓的,因為這些點看起來像圓上的一條弧
所以為了弄清楚這個奇怪的手勢里都發(fā)生了什么,你可以把擬合圓在屏幕上繪制出來。
把CircleDrawView.swift 中 drawDebug
的值設(shè)置為 true
:
var drawDebug = true // 設(shè)置成true將展示擬合相關(guān)的其他信息
這段代碼會把擬合圓的一些其他信息繪制到屏幕上。
如果要將擬合細(xì)節(jié)更新到視圖上,把如下代碼分支添加到 GameViewController.swift 中的 circled(_:)
方法:
if c.state == .Ended || c.state == .Failed || c.state == .Cancelled {
circlerDrawer.updateFit(c.fitResult, madeCircle: c.isCircle)
}
再次構(gòu)建并運(yùn)行app;畫一個圓形路徑,當(dāng)你抬起手指時,擬合圓就被繪制到屏幕上,如果擬合成功就是綠色,擬合失敗就是紅色:
接下來會講一點點其他方面的事情。
回到被標(biāo)記錯的形狀,為什么這些非圓形手勢會被處理?擬合在兩種情況下顯然會出錯:當(dāng)繪制的形狀在圓內(nèi)部有點,以及繪制的形狀不是一個完整的圓時。
對于像類似S,漩渦,數(shù)字8等等對稱的形狀。擬合得到的誤差非常小,但是很顯然它們都不是圓。這就是數(shù)學(xué)近似法和一個可用手勢之間的差距。一個明顯的修復(fù)方式就是排除所有在圓內(nèi)部存在點的路徑。
你可以通過檢查所有的觸摸點,看是否有點是在擬合圓內(nèi)部的方式來解決這個問題。
把下面的輔助方法加到 CircleGestureRecognizer.swift中:
private func anyPointsInTheMiddle() -> Bool {
// 1
let fitInnerRadius = fitResult.radius / sqrt(2) * tolerance
// 2
let innerBox = CGRect(
x: fitResult.center.x - fitInnerRadius,
y: fitResult.center.y - fitInnerRadius,
width: 2 * fitInnerRadius,
height: 2 * fitInnerRadius)
// 3
var hasInside = false
for point in touchedPoints {
if innerBox.contains(point) {
hasInside = true
break
}
}
return hasInside
}
這段代碼對根據(jù)圓擬合出來的一個較小矩形禁區(qū)進(jìn)行檢查。如果有某個點出現(xiàn)在這個矩形中那么這個手勢就失效了。上述代碼做了如下的事情:
tolerance
將為散亂,但合理的圓提供足夠的空間,但是也有足夠的控件來排除那些正中間有點的非圓形狀。innerBox
內(nèi)。
下一步,修改 touchesEnded(_:withEvent:)
,把如下代碼添加到 isCircle
的判斷中:
override func touchesEnded(touches: Set<NSObject>!, withEvent event: UIEvent!) {
super.touchesEnded(touches, withEvent: event)
// 用戶停止觸摸,判斷路徑是否組成了一個圓
fitResult = fitCircle(touchedPoints)
// 保證沒有點在圓的中間
let hasInside = anyPointsInTheMiddle()
isCircle = fitResult.error <= tolerance && !hasInside
state = isCircle ? .Ended : .Failed
}
這段代碼使用這個檢測方法來判斷圓中間是否有點,如果有,那么就檢測不到圓。
構(gòu)建并運(yùn)行。嘗試畫一個‘S’形狀,你會發(fā)現(xiàn)它將不能被識別。太贊了!:]
現(xiàn)在你已經(jīng)對非圓的弧形進(jìn)行了處理,那些被當(dāng)作超大圓一部分的討厭短弧怎么辦?如果你在調(diào)試?yán)L制時觀察過,路徑(黑框內(nèi))和擬合圓的尺寸差距是非常巨大的:
被識別成圓的路徑至少要和圓本上的尺寸差不太多:
修復(fù)這個問題只需要簡單滴把路徑的大小和擬合圓的大小做比較就可以了。
把下面的輔助方法添加到 CircleGestureRecognizer.swift中:
private func calculateBoundingOverlap() -> CGFloat {
// 1
let fitBoundingBox = CGRect(
x: fitResult.center.x - fitResult.radius,
y: fitResult.center.y - fitResult.radius,
width: 2 * fitResult.radius,
height: 2 * fitResult.radius)
let pathBoundingBox = CGPathGetBoundingBox(path)
// 2
let overlapRect = fitBoundingBox.rectByIntersecting(pathBoundingBox)
// 3
let overlapRectArea = overlapRect.width * overlapRect.height
let circleBoxArea = fitBoundingBox.height * fitBoundingBox.width
let percentOverlap = overlapRectArea / circleBoxArea
return percentOverlap
}
這個方法計算出用戶的路徑和擬合圓有多少是重疊的:
CGMutablePath
路徑變量的一部分,所以可以使用 CGPathGetBoundingBox
方法來處理棘手的數(shù)學(xué)問題。CGRect
的 rectByIntersecting
方法來計算出兩個矩形路徑的重疊部分。
下一步,修改 touchesEnded(_:withEvent:)
中對 isCircle
的判斷,如下:
let percentOverlap = calculateBoundingOverlap()
isCircle = fitResult.error <= tolerance && !hasInside && percentOverlap > (1-tolerance)
構(gòu)建并運(yùn)行app;只有合理的圓形才能通過測試。你可以想盡辦法愚弄它!:]
你是否有注意到之前測試?yán)L制這節(jié)對 .Cancelled
的檢查?觸摸會在有系統(tǒng)告警、在手勢識別器被某個代理明確取消、在觸摸中途被置為不可用時被取消掉。除了更新狀態(tài)機(jī),不需要為圓形識別器做更多的事情。把下面的代碼片段添加到CircleGestureRecognizer.swift:
override func touchesCancelled(touches: Set<NSObject>!, withEvent event: UIEvent!) {
super.touchesCancelled(touches, withEvent: event)
state = .Cancelled // 提前設(shè)置為取消狀態(tài)
}
這段代碼在觸摸事件被取消時將 state
置為 .Cancelled
。
當(dāng)程序運(yùn)行時,點擊New Set。發(fā)現(xiàn)什么了么?對,按鈕不能用了!這是因為手勢識別器吃掉了所有的點擊事件!
使手勢識別器與其他控件正常交互的方式有幾種。首選的方式是使用 UIGestureRecognizerDelegate
來重寫默認(rèn)行為。
打開 GameViewController.swift,在 viewDidLoad(_:)
中把手勢識別器的 delegate
設(shè)置為 self
:
circleRecognizer.delegate = self
現(xiàn)在在文件的底部添加如下的擴(kuò)展來實現(xiàn)代理方法:
extension GameViewController: UIGestureRecognizerDelegate {
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
// 允許點擊按鈕
return !(touch.view is UIButton)
}
}
這段代碼阻止手勢識別器去識別按鈕上的觸摸事件;而繼續(xù)讓按鈕本身去處理觸摸。代理方法還有好幾個,這些代理方法可以用來自定義手勢識別器在視圖層級中的行為方式。再次構(gòu)建并運(yùn)行app;點擊按鈕就可以正常使用了。
剩下的就是處理交互細(xì)節(jié),讓游戲看起來是精心打磨過的。
首先,在某個圖片被圈中之后你要阻止用戶繼續(xù)和視圖交互。否則,在等待新一組圖片出現(xiàn)時路徑仍然會繼續(xù)更新。
打開GameViewController.swift,把如下代碼添加到 selectImageViewAtIndex(_:)
的底部:
circleRecognizer.enabled = false
現(xiàn)在在 startNewSet(_:)
方法的底部讓手勢識別器重新生效,從而繼續(xù)處理下一輪:
circleRecognizer.enabled = true
下一步,把如下代碼加到 circled(_:)
的 .Began
分支中:
if c.state == .Began {
circlerDrawer.clear()
goToNextTimer?.invalidate()
}
這段代碼添加了一個計時器,這個計時器會在短暫的延遲后清除掉路徑,從而使用戶在延遲時間內(nèi)還能重新嘗試。
同時把如下代碼添加在 circled(_:)
方法的最終狀態(tài)檢測中:
if c.state == .Ended || c.state == .Failed || c.state == .Cancelled {
circlerDrawer.updateFit(c.fitResult, madeCircle: c.isCircle)
goToNextTimer = NSTimer.scheduledTimerWithTimeInterval(afterGuessTimeout, target: self, selector: "timerFired:", userInfo: nil, repeats: false)
}
這段代碼在手勢識別器的狀態(tài)變成結(jié)束、失敗或者取消時設(shè)置一個短時間內(nèi)啟動的計時器。
最后,在 GameViewController 中添加如下方法:
func timerFired(timer: NSTimer) {
circlerDrawer.clear()
}
這段代碼在計時器啟動時清除圓形,這樣用戶就知道畫另外一個圓形來再次嘗試。
構(gòu)建并運(yùn)行app;如果手勢不是近似的一個圓,你會發(fā)現(xiàn)路徑在短暫的延遲后就會自動被清除掉。
你可以在這里下載教程的完整項目。
現(xiàn)在你已經(jīng)為你的游戲做了一個簡單但功能強(qiáng)大的圓形手勢識別器。你可以把這個概念進(jìn)行延伸來識別其他形狀,甚至可以自定義圓擬合算法來適應(yīng)其他需求。
如果想要了解更多,可以查閱蘋果官方文檔中關(guān)于Gesture Recognizers的章節(jié)。
如果你對本教程有任何疑問和評論,請在論壇下方的評論區(qū)自由發(fā)言!
更多建議: