Square Engineering Blog 上有一篇很棒的文章,為 Android 帶來了更流暢的簽名,但我沒有找到任何與 iOS 相關的介紹。所以,在 iOS 裝置上捕捉使用者簽名的最好方式是什麼呢?
雖然我沒有找到關於簽名捕捉的文章,但 App Store 上已有一些應用很好地實現了簽名捕捉。我的目標使用者體驗是達到 iPad 應用 Paper by 53 那樣的效果(它是一個繪圖應用,有著漂亮且極具響應性的筆刷)。
本文所有的程式碼都可以在 Github 倉庫 SignatureDemo 裡找到。
點點相連
最簡單的方式是捕捉使用者的觸控點並用直線將它們連線在一起。
在 UIView
子類的初始化方法中,建立一個 path ,以及用於捕捉觸控事件的手勢識別器。
1 2 3 4 5 6 7 |
// 建立一個 path 以連線各點 path = [UIBezierPath bezierPath]; // 捕捉觸控 UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)]; pan.maximumNumberOfTouches = pan.minimumNumberOfTouches = 1; [self addGestureRecognizer:pan]; |
捕捉 pan 手勢的觸控點,並通過連線將其放入 bézier path 裡:
1 2 3 4 5 6 7 8 9 10 |
- (void)pan:(UIPanGestureRecognizer *)pan { CGPoint currentPoint = [pan locationInView:self]; if (pan.state == UIGestureRecognizerStateBegan) { [path moveToPoint:currentPoint]; } else if (pan.state == UIGestureRecognizerStateChanged) [path addLineToPoint:currentPoint]; [self setNeedsDisplay]; } |
畫出 path:
1 2 3 4 5 |
- (void)drawRect:(CGRect)rect { [[UIColor blackColor] setStroke]; [path stroke]; } |
上圖是用這種技術畫出的字母 “J” ,它也揭示了一些問題:在較慢的速度下, iOS 可以捕捉到足夠多的觸控點,因此看起來較平滑,但稍快的移動就會導致觸控點之間較大的間距,簽名看起來就是一些直線段相連。
2012 年的 Apple 開發者大會有一個 session 叫做 建立高階手勢識別器,它用了一點數學來解決此問題。
二次貝塞爾曲線
作為用線段連線各觸控點的代替,我們改用二次貝塞爾曲線來連線這些點,使用前述的 WWDC session 上討論的技術(定位到 42 分 15 秒)。用二次貝塞爾曲線來連線觸控點,使用觸控點作為控制點以及中間點作為起點和終點。
新增二次曲線到之前的程式碼裡要求儲存前一個觸控點,所以要新增一個例項變數:
1 |
CGPoint previousPoint; |
並建立一個函式來計算兩點的中間點:
1 2 3 4 5 6 |
static CGPoint midpoint(CGPoint p0, CGPoint p1) { return (CGPoint) { (p0.x + p1.x) / 2.0, (p0.y + p1.y) / 2.0 }; } |
再更新 pan 手勢處理器,用二次曲線替換直線:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
- (void)pan:(UIPanGestureRecognizer *)pan { CGPoint currentPoint = [pan locationInView:self]; CGPoint midPoint = midpoint(previousPoint, currentPoint); if (pan.state == UIGestureRecognizerStateBegan) { [path moveToPoint:currentPoint]; } else if (pan.state == UIGestureRecognizerStateChanged) { [path addQuadCurveToPoint:midPoint controlPoint:previousPoint]; } previousPoint = currentPoint; [self setNeedsDisplay]; } |
並沒有改寫太多程式碼,但我們已經看到了巨大的不同。觸控點不再可見,但對於捕捉簽名來說,它依然有些乏味。每個曲線都是同樣的寬度,這不符合實際用鋼筆簽名的感覺。
可變的線寬
基於觸控速度,寬度可變,這樣就能建立出更加自然的筆畫。 UIPanGestureRecognizer
已經包含有一個叫做 velocityInView:
的方法,它用一個 CGPoint
返回當前觸控的速度。
為了渲染出一個可變寬度的筆畫,我切換到 OpenGL ES 和一個能轉換筆畫為三角形(具體說來,是三角帶(triangle strip))的叫做鑲嵌(tesselation)的技術(OpenGL 支援畫線,但 iOS 不支援平滑過渡的可變寬度)。沿曲線的二次分點同樣需要被計算,但這超出了本文討論的範疇。還請看看 Github 上的原始碼瞭解細節吧。
給定兩個點,計算出一個垂直的向量,它的大小設定為當前厚度的一半。基於 GL_TRIANGLE_STRIP 的自然特性,只需要兩個點就能用兩個三角形去建立下一個矩形。
下面是使用二次貝塞爾曲線,並根據速度調節筆畫粗細而創造出的一個具有視覺吸引力且非常自然的簽名。