透過CAShapeLayer和UIBezierPath製作簡易簽名板
・前言:
在專案中,經常需要使用者的簽名圖檔,尤其是金融相關的案子更是基本需求,不論是簽署保單、同意書簽署等,相關功能都相當常見,下面就將透過CAShapeLayer和UIBezierPath做一個簽名板。
・建立專案:
打開Xcode -> File -> New -> Project ...
選擇iOS -> Single View App -> Next
輸入專案名稱:SignatureDemo -> Next...
這樣就建立了Demo 的專案了
・實作專案:
首先,新增一個View,File -> New -> File...
選擇iOS -> Cocoa Touch Class -> Next
輸入View名稱:SignatureView -> Next
這個 SignatureView就是我們的簽名板,等等我們就是要將功能實作在這裡
在實作簽名板功能之前,我們先來對ViewController做一些相關設定
點選 Main.Storyboard -> ViewCotroller -> 點選+號 -> 選取View -> 將View拖拉到ViewController中
如下圖所示:
將剛剛加入的View 的Custom Class 中的Class設定為SignatureView
並加入適當的Constraints,我這裡設定的是:對上距離0、對左距離0、對右距離0、對下距離100
如下圖所示:
加入SignatureView之後,再加入兩個Button
這兩個Button的用途分別為清除簽名以及將簽名存入手機相簿
點選 Main.Storyboard -> ViewCotroller -> 點選+號 -> 選取Button -> 將Button拖拉到ViewController中
如下圖所示:
加入適當的Constraints和Button的外觀
我這邊設定的是:(清除簽名按鈕)對上距離10、對左距離0、對右距離0、高度40
(存入手機相簿按鈕)對上距離5、對左距離0、對右距離0、高度40
如下圖所示:
將需要使用的元件新增且把樣式設定完畢後,再來就是要加入元件和程式的關聯了(IBOutlet & IBAction func)
加入SignatureView與程式的關聯:
點選左側SignatureView並按下滑鼠右鍵不放,將游標拖移到" class ViewController: UIViewController{ "這行底下後放開滑鼠右鍵,就會像下圖一樣跳出一個框框,輸入SignatureView的名字,我這邊命名為signatureView(注意:這裡的名稱開頭為小寫)這樣就建立了SignatureView和程式的關聯了
(宣告了一個Class為SignatureView的物件,變數名稱叫做signatureView)
如下圖所示:
加入Button與程式的關聯:
和剛剛SignatureView的做法一樣,點選左側清除簽名的Button並按下滑鼠右鍵不放,將游標拖移到第19行Override func viewDidLoad()這個方法的結束點 " }" 下面一行後放開滑鼠右鍵,一樣會跳出一個小框框,輸入這個方法的名稱,我這裡設定為clearButtonPressed,另外一個存入手機相簿的Button做法也是一樣,我這裡將他的方法命名為saveButtonPressed
如下圖所示:
這樣就完成元件與程式的關聯了,再來就是要寫程式囉。
・實作簽名板:
在開始實作之前,我們先思考一下實作簽名的功能
1.這次的簽名板是設定只能一隻筆簽名
2.簽名的時候不能超過簽名板的範圍
3.簽名的顏色
4.筆的寬度
5.顯示簽名
6.清除簽名
7.將簽名存入手機相簿
將以上這些功能是簽名板所需的基本功能,一一實作後就能完成簡易的簽名板囉
1.這次的簽名板是設定只能一隻筆簽名:
因為ios預設是支援多點觸碰,所以只要將簽名板(SignatureView)的多點觸碰關閉就好囉。
//將signatureView的多點觸碰功能關閉
signatureView.isMultipleTouchEnabled = false
2.簽名的時候不能超過簽名板的範圍:
簽名的時候超出簽名板的範圍就無效囉。
//設定只顯示signatureView的範圍,超過的裁切掉
signatureView.clipsToBounds = true
點選ViewController.Swift -> 在Override func ViewDidLoad()這個方法底下加入上面這兩項設定
接著點選SignatureView.Swift 做以下設定
3.簽名的顏色:
4.筆的寬度:
對於兩項功能,我們先對簽名板(SignatureView)新增一些屬性
class SignatureView: UIView {
var lineColor = UIColor.black //預設簽名顏色為黑色
var lineWidth:CGFloat = 2 //預設簽名寬度為2
var path:UIBezierPath? //紀錄簽名路徑
var touchPoint:CGPoint? //所有簽名座標
var startPoint:CGPoint? //開始簽名座標
}
5.顯示簽名
顯示簽名的功能,我們拆解其功能為:開始簽名、簽名、顯示
首先我們先override這兩項UIView的方法
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
//開始簽名
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
//正在簽名
}
在 override func touchesBegan(_ touchL Set<UITouch>, with event: UIEvent?)這個方法中將第一個座標指定給startPoint
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
//開始簽名,將第一個接觸到的座標指定給startPoint
startPoint = touches.first?.location(in: self)
}
再透過 override func touchMoved(_ touches: Set<UITouch>, with event: UIEvent?)這個方法將所有簽名時所接觸到的座標指定給touchPoint
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
//正在簽名,將所有簽名時的座標指定給touchPoint
touchPoint = touches.first?.location(in: self)
}
接著,將取到的touchPoint座標和startPoint座標之間畫一條線,並記錄在path裡,畫完之後再將touchPoint座標指定給startPoint座標再繼續畫線
startPoint --> touchPoint(startPoint) --> touchPoint(startPoint) --> touchPoint
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
//正在簽名,將所有簽名時的座標指定給touchPoint
touchPoint = touches.first?.location(in: self)
//初始化path,在startPoint到touchPoint畫一條線
//可以想像成move是開始的座標,addline是下一個的座標
//畫完之後再將下一個座標指定成為開始的座標,這樣就能畫出連續的線
// startPoint --> touchPoint (startPoint) --> touchPoint (startPoint) --> touchPoint
path = UIBezierPath()
path?.move(to: startPoint ?? CGPoint.init())
path?.addLine(to: touchPoint ?? CGPoint.init())
startPoint = touchPoint
}
再來將所有路徑透過CAShapeLayer畫出
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
//正在簽名,將所有簽名時的座標指定給touchPoint
touchPoint = touches.first?.location(in: self)
//初始化path,在startPoint到touchPoint畫一條線
//可以想像成move是開始的座標,addline是下一個的座標
//畫完之後再將下一個座標指定成為開始的座標,這樣就能畫出連續的線
// startPoint --> touchPoint (startPoint) --> touchPoint (startPoint) --> touchPoint
path = UIBezierPath()
path?.move(to: startPoint ?? CGPoint.init())
path?.addLine(to: touchPoint ?? CGPoint.init())
startPoint = touchPoint
draw()
}
func draw() {
let shape = CAShapeLayer() //初始化 CAShapeLayer()
shape.path = path?.cgPath //設定 CAShapeLayer要畫出的路徑
shape.strokeColor = lineColor.cgColor //設定線條的顏色
shape.lineWidth = lineWidth //設定線條的寬度
self.layer.addSublayer(shape) //將設定的屬性增加到SignatureView的layer
self.setNeedsLayout() //更新畫面
}
這樣就完成了簽名板的基本功能了
6.清除簽名
實作了簽名板功能後,萬一不小心手誤簽錯了該怎麼,我們只要將所有路徑及SignatureView的Layer清空就可以了
func clearView() {
path?.removeAllPoints() //移除UIBezierPath所有座標
self.layer.sublayers = nil //將SignatureView的layer清除
self.setNeedsDisplay() //更新畫面
}
7.將簽名存入手機相簿
簽完名之後透過螢幕快照的方式將簽名檔存入手機相簿內
一定要做這個步驟:取得存取相簿權限,在Info.plist加入NSPhotoLibraryAddUsageDescription
<key>NSPhotoLibraryAddUsageDescription</key>
<string>取得相機權限</string>
回到ViewController.Swift 將螢幕快照的方法加入saveButtonPressed這個方法裡面,
也就是說按下“存入手機相簿”的按鈕的時候,執行螢幕快照的功能
@IBAction func saveButtonPressed(_ sender: Any) {
var screenshotImage :UIImage?
let layer = UIApplication.shared.windows.first?.layer
let scale = UIScreen.main.scale
UIGraphicsBeginImageContextWithOptions(layer!.frame.size, false, scale);
guard let context = UIGraphicsGetCurrentContext() else { return }
layer!.render(in:context)
screenshotImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
if let image = screenshotImage {
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
popAlert()
}
}
func popAlert() {
let controller = UIAlertController(title: "提示", message: "已存入手機相簿", preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default) { (_) in
self.signatureView.clearView()
}
controller.addAction(okAction)
present(controller, animated: true, completion: nil)
}
螢幕快照成功後跳出alert提醒使用者儲存成功
・結論:
在IOS的App開發中一定很多種製作簽名版的方式,
而透過CAShapeLayer和UIBezierPath所製作的簽名板只是其中一項
也許將來Apple 會直接推出簽名版的API供開發者使用也說不定
就讓我們期待吧!
・參考資料:
https://developer.apple.com/documentation/uikit/uibezierpath
https://developer.apple.com/documentation/quartzcore/cashapelayer
https://stackoverflow.com/questions/25448879/how-do-i-take-a-full-screen-screenshot-in-swift