我們已經(jīng)學習完了Quartz2D的一些基本的用法硫豆,在實際開發(fā)過程中龙巨,經(jīng)常使用Quartz2D,可以幫助我們少使用蘋果自帶的控件熊响,直接畫圖到上下文旨别,對系統(tǒng)的性能是一個非常好的優(yōu)化方式。Quartz2D的功能強大汗茄,絕逼不是畫線秸弛,繪制圖片那么easy,今天講一下他在實際項目中的應用洪碳,順便將思路理清楚递览,方便大家看涂鴉板demo,還有手勢解鎖
文章中的幾個demo
- 1.使用圖形上下文制作涂鴉板
- 2.使用貝塞爾路徑制作涂鴉板
- 3.手勢解鎖
下面詳細的介紹一下項目的思路
一.使用圖形上下文制作涂鴉板
分析
1.涂鴉板實際上就是繪制很多的線條
2.保存線條非迹,使用可變數(shù)組
3.使用上下文繪制圖片,使用drawRect方法
4.和屏幕交互纯趋,應該使用touchesBegin方法
代碼分析
1.自定義一個DBPainterView
2.在view中生成一個可變數(shù)組作為變量,懶加載處理,可以供程序使用
//MARK: - 懶加載屬性
//用于盛放所有單個路徑的數(shù)組
private lazy var pointArr:NSMutableArray = {
return NSMutableArray()
}()
3.實現(xiàn)touchesBegin吵冒,touchesMoved,touchesEnd方法
//MARK: - 重寫touch三個方法
//touchBegin
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first
let startPoint = touch?.locationInView(touch?.view)
let linePathArr = NSMutableArray()
linePathArr.addObject(NSValue.init(CGPoint: startPoint!))
pointArr.addObject(linePathArr)
setNeedsDisplay()
}
//touchMoved
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first
let startPoint = touch?.locationInView(touch?.view)
let lastLineArr = pointArr.lastObject
lastLineArr!.addObject(NSValue.init(CGPoint: startPoint!))
setNeedsDisplay()
}
//touchEnd
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first
let startPoint = touch?.locationInView(touch?.view)
let lastLineArr = pointArr.lastObject
lastLineArr!.addObject(NSValue.init(CGPoint: startPoint!))
setNeedsDisplay()
}
代碼分析纯命,
3.1.touchesBegin
就是開始繪制,現(xiàn)在沒有拿到路徑的具體的點痹栖,所以我們應該給每一個路勁用一個小數(shù)組保存所有點的數(shù)組** linePathArr(保存每一根line的數(shù)組)亿汞,每一次調(diào)用都應該是創(chuàng)建一個新的路徑(新的linePathArr),然后加到保存所有路徑的數(shù)組中( pointArr保存了所有l(wèi)ine的數(shù)組)揪阿,然后調(diào)用setNeedsDisplay
方法疗我,繪制路徑
3.2.touchesMoved
方法是手指在屏幕移動的時候調(diào)用的,頻率最高南捂,就是一直在添加point吴裤,說白了,就是給最新添加的那個路徑添加點溺健,所以應當找到數(shù)組中最后一個路徑麦牺,然后給這個路徑添加point,let lastLineArr = pointArr.lastObject
,lastLineArr!.addObject(NSValue.init(CGPoint: startPoint!))
3.3.touchEnd
方法和2**的事情是一樣的鞭缭,所以可以提煉一下代碼剖膳,我就不寫了
4.繪制圖片 drawRect
override func drawRect(rect: CGRect) {
let ctx = UIGraphicsGetCurrentContext()
for index in 0 ..< pointArr.count
{
//獲取單根線
let linePathArr = pointArr.objectAtIndex(index)
for j in 0 ..< linePathArr.count
{
let point = linePathArr.objectAtIndex(j).CGPointValue()
if j == 0 {
CGContextMoveToPoint(ctx, point.x, point.y)
}else
{
CGContextAddLineToPoint(ctx, point.x, point.y)
}
}
}
//設置上下文的屬性
CGContextSetLineWidth(ctx, 3)
UIColor.redColor().set()
CGContextSetLineCap(ctx, CGLineCap.Round)
//渲染
CGContextStrokePath(ctx)
}
}
4.1 首先遍歷大數(shù)組A,獲取每一條線(所有點)的數(shù)組B岭辣,遍歷B中所有的點吱晒,但是B中的第一個b[0]應該是調(diào)用CGContextMoveToPoint
,b[其他]應當調(diào)用CGContextAddLineToPoint
方法,
4.2.可以設置一下圖形上下文的屬性沦童,最后渲染就好了.
4.3 可以設置好多種顏色枕荞,使用圖形上下文棧就可以實現(xiàn)
5.DBPainterView 對外實現(xiàn)的“上一步”,"清空",“保存”功能
//刪除
func clear(){
pointArr.removeAllObjects()
setNeedsDisplay()
}
//上一步
func preview()
{
pointArr.removeLastObject()
setNeedsDisplay()
}
//保存到本地
func saveToAbum() {
//保存圖片的事件
UIGraphicsBeginImageContextWithOptions(self.frame.size, false, 0.0)
let ctx = UIGraphicsGetCurrentContext()
self.layer.renderInContext(ctx!)
//獲取圖片
let image = UIGraphicsGetImageFromCurrentImageContext()
//結(jié)束位圖上下文
UIGraphicsEndImageContext()
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
}
代碼太簡單搞动,就不解釋了哈
二.使用貝塞爾路徑制作涂鴉板
剛才使用了圖形上下文繪制路徑躏精,感覺還行,但是可以簡化鹦肿,剛才說的將一個路徑的所有點放到路徑的數(shù)組中矗烛,然后根據(jù)點來繪制,可以理解箩溃,但是會很麻煩瞭吃,因為底層就是通過CGContextPathRef繪制路徑的,因為CGContextPathRef是C語言涣旨,大數(shù)組不能添加它歪架,所以我們放棄,然后選擇貝塞爾路徑,他是oc中對象霹陡,非常適合制作涂鴉板
//繪制一條路徑的寫法和蚪,非常的簡單
let path = UIBezierPath()
path.moveToPoint(CGPoint.init(x: 9, y: 9))
path.addLineToPoint(CGPoint.init(x: 40, y: 50))
path.stroke()
使用貝塞爾路徑制作涂鴉板的步驟(和圖形上下文基本一致)
- 1.懶加載一個用來橙裝所有路徑的數(shù)組pathArr
- 2.touchesBegin的時候止状,生成一個路徑,調(diào)用moveToPoint方法攒霹,添加起點怯疤,將path保存到數(shù)組中
- 3.更改線寬和更改線的顏色,要個自定義的view設置lineWidth催束,和lineColor這個屬性集峦,最后要去給path設置這兩個屬性
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first
let startPoint = touch?.locationInView(touch?.view)
//1.創(chuàng)建路徑
let path = UIBezierPath()
//2.設置起點
path.moveToPoint(startPoint!)
//3.將path,添加到pathArr上
pathArr.addObject(path)
//4.繪圖
setNeedsDisplay()
}
- 3.touchesMoved和touchesEnd方法功能一致抠刺,就合二為一了塔淤,就是獲取大數(shù)組中最后一個路徑,然后調(diào)用
addLineToPoint
方法
//touchMoved
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
addPointToPath(touches)
}
//touchEnd
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
addPointToPath(touches)
}
//touchMoved和touchEnd統(tǒng)一的代碼
private func addPointToPath(touches: Set<UITouch>){
let touch = touches.first
let movePoint = touch?.locationInView(touch?.view)
//獲取最后一個path
let path = pathArr.lastObject as! UIBezierPath;
path.addLineToPoint(movePoint!)
setNeedsDisplay()
}
4.繪制路徑
override func drawRect(rect: CGRect) {
//繪制線條
for index in 0 ..< pathArr.count
{
let path = pathArr[index] as! UIBezierPath
path.stroke()
}
}
5.添加線寬和線顏色的屬性
//設置一個變量速妖,用來存儲線寬
var lineWidth:CGFloat = 2;
//設置一個變臉高蜂,用來存儲線顏色
var lineColor:UIColor = UIColor.blackColor();
5.1 我們要將顏色和寬的的屬性使用到以后的線上,不能影響到過去的买优,所以妨马,應該在生成一個path的時候,直接設置他的這兩個屬性杀赢,因為path中沒有l(wèi)ineColor這個屬性烘跺,所以自定義一個DBBezierPath
class DBBezierPath: UIBezierPath {
var lineColor:UIColor?
}
5.2 重新修改一下touchesBegin方法
//touchBegin
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first
let startPoint = touch?.locationInView(touch?.view)
//1.創(chuàng)建路徑
let path = DBBezierPath()
path.lineWidth = lineWidth
//2.設置線條的顏色
path.lineColor = lineColor
//2.設置起點
path.moveToPoint(startPoint!)
//3.將path,添加到pathArr上
pathArr.addObject(path)
//4.繪圖
setNeedsDisplay()
}
5.3 在渲染的時候脂崔,我們要將自定義的lineColor
取出來滤淳,渲染
override func drawRect(rect: CGRect) {
//繪制線條
for index in 0 ..< pathArr.count
{
let path = pathArr[index] as! DBBezierPath
path.lineColor!.set()
path.stroke()
}
}
5.4 這樣就可以制作出彩色的畫板了,而且其他保存砌左,上一步等功能都可以正常使用
使用了貝塞爾路徑脖咐,遠離了兩個數(shù)組,運行和理解起來超級簡單汇歹?
三.手勢解鎖
思路和注意點
- 1.創(chuàng)建基本的九宮格樣式UI
- 2.抽取方法類和自定義一個button(注意
btn.userInteractionEnabled = false
)- 3.提出工具方法
3.1 獲取當前的觸摸點
3.2 判斷當前點是不是在btn中- 4.保存選中的所有按鈕
- 5.通過選中的按鈕連線
5.1 防止數(shù)組中多次添加同一個button- 6.使用touchesEnd方法清空數(shù)組
6.1 給drawRect方法添加判空的條件
6.2 重新繪制狀態(tài)
6.3 使用makeObjectsSelect方法讓所有的button的選中狀態(tài)為NO- 7.繪制最后一個按鈕和手指移動的地點的連線
7.1 在touchesMoved方法中保存手指的所在的point
7.2 在drawRect方法中鏈接最后一個按鈕和point
7.3 設置bezierPath的基本屬性
7.4 解決剛剛點擊第一個按鈕屁擅,但是連線到CGPointZore的bug(在TouchBegin中清空,在drawRect判斷)- 8.減小觸摸button的響應范圍
- 9.拼接用戶的觸摸路徑
- 10.添加代理方法产弹,讓外界知道用戶的觸摸路徑(給代理添加IBOut)
- 11.修改連線的具體顏色
代碼講解分析
1.創(chuàng)建基本的九宮格樣式UI
- 1.1 自定義一個GULockView派歌,然后使用經(jīng)典九宮格算法實現(xiàn)
//設置初始化函數(shù),創(chuàng)建9個按鈕
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setUpBsicUI()
}
//初始化函數(shù)
private func setUpBsicUI()
{
//創(chuàng)建九個按鈕
for index in 0 ..< 9
{
let btn = GUButton.init(type: UIButtonType.Custom);
addSubview(btn)
btn.tag = index
btn.addTarget(self, action: "btnClick:", forControlEvents: UIControlEvents.TouchUpInside)
}
}
- 1.2 布局UI
//設置九個按鈕的位置
override func layoutSubviews() {
super.layoutSubviews()
//經(jīng)典的九宮格算法
let totalColume = 3
let bWidth:CGFloat = 74
let bHeight:CGFloat = bWidth
let margin = (self.frame.width - bWidth * CGFloat(totalColume))/CGFloat(totalColume+1)
//自己的高度是bHeight*3
for index in 0 ..< self.subviews.count
{
let currentRow = index/totalColume
let currentColumn = index%totalColume
let bX = margin + (CGFloat(currentColumn) * (bWidth+margin))
let bY = CGFloat(currentRow) * (bHeight+margin)
let btn = self.subviews[index]
btn.frame = CGRectMake(bX, bY, bWidth, bHeight)
}
}
2.抽取方法類和自定義一個button
- 2.1 自定義一個GUButton,設置內(nèi)部的圖片等樣式,
setImage(UIImage.init(named: "gesture_node_normal"), forState: UIControlState.Normal)
setImage(UIImage.init(named: "gesture_node_highlighted"), forState: UIControlState.Selected)
setImage(UIImage.init(named: "gesture_node_disable"), forState: UIControlState.Disabled)
contentMode = UIViewContentMode.Center
userInteractionEnabled = false
- 2.2
btn.userInteractionEnabled = false
一定要寫
這個涉及到了時間傳遞痰哨,我們馬上要去實現(xiàn)觸摸的三個方法胶果,如果手勢路過btn,恰巧userInteractionEnabled = ture
斤斧,那么手勢直接讓btn截獲早抠,那么GULockView
獲取不到手勢,造成了問題撬讽,所以一定要設置為no蕊连,(如果就是不想設置為no悬垃,其實也可以在自定義的CGButton中實現(xiàn)touches三個方法,調(diào)用super.touches三個方法往上傳遞咪奖,不推薦)
3.提出工具方法盗忱,設置btn被選中的條件
3.1 獲取當前的觸摸點
/**
獲取是否當前觸摸點的坐標
:returns: 所在的點的坐標
*/
private func pointWithTouches(touches: Set<UITouch>) -> CGPoint?
{
let touch = touches.first
let locPoint = touch?.locationInView(touch?.view)
return locPoint
}
3.2 判斷當前點是不是在btn中
/**
判斷是否選中的點的依據(jù)
:param: point 當前點
:returns: 所在的按鈕酱床,可能沒有
*/
private func buttonWithPoint(point:CGPoint) -> UIButton?
{
//遍歷數(shù)組羊赵,看看是不是在9個按鈕的里面
for index in 0 ..< self.subviews.count
{
let b = self.subviews[index] as! UIButton
let isIn = CGRectContainsPoint(b.frame, point)
if isIn {
return b
}
}
return nil
}
3.2 設置btn被選中的狀態(tài)
//touchesMoved 和 touchesBegin此刻的內(nèi)容都是這個
override func touchesMoved(touches: Set<UITouch>,
withEvent event: UIEvent?) {
let point = pointWithTouches(touches)
let locBtn = buttonWithPoint(point!)
if (locBtn != nil) {
locBtn?.selected = true
}
setNeedsDisplay()
}
4.保存選中的所有按鈕
可以通過一個數(shù)組來保存所有的按鈕
//MARK: - 懶加載數(shù)組
private lazy var btns:NSMutableArray = NSMutableArray()
在touchesBegan
和touchesMoved
方法中添加所選擇的按鈕
if (locBtn != nil) {
locBtn?.selected = true
//添加到數(shù)組中
btns.addObject(locBtn!)
}
5.通過選中的按鈕連線
5.1 防止數(shù)組中多次添加同一個button
解決方法
//在判斷的時候扇谣,添加一個條件昧捷,是否selected == false
if (locBtn != nil && locBtn?.selected == false) {
locBtn?.selected = true
//添加到數(shù)組中
btns.addObject(locBtn!)
}
6.使用touchesEnd方法清空數(shù)組
//手勢釋放時,要做的事情
//1.應當清空數(shù)組罐寨,
//2.應當將所選中的按鈕全部設置為selected == false
//3.重新繪制
//遍歷所有的數(shù)組靡挥,是他的select == false
for index in 0 ..< btns.count
{
let btn = btns[index] as! UIButton
btn.selected = false
}
btns.removeAllObjects()
setNeedsDisplay()
6.1 給drawRect方法添加判空的條件
//算是優(yōu)化吧,已經(jīng)入drawRect方法鸯绿,首先去判斷一下是不是空的
if btns.count == 0 {
return
}
6.2 重新繪制狀態(tài)
override func drawRect(rect: CGRect) {
if btns.count == 0 {
return
}
//使用UIBezierPath繪制路徑
let bezierPath = UIBezierPath()
for index in 0 ..< btns.count
{
//獲取每一個點
let btn = btns[index]
if index == 0 {
bezierPath.moveToPoint(btn.center)
}else{
bezierPath.addLineToPoint(btn.center)
}
}
bezierPath.lineWidth = 10
bezierPath.lineJoinStyle = CGLineJoin.Round
UIColor.blueColor().set()
bezierPath.stroke()
}
7.繪制最后一個按鈕和手指移動的地點的連線
7.1 在touchesMoved方法中保存手指的所在的point
/// 當前觸摸點,默認是零
private var currentPoint:CGPoint = CGPointZero
7.2 在drawRect方法中鏈接最后一個按鈕和point
//drawRect方法中跋破,可以這樣實現(xiàn)
bezierPath.addLineToPoint(currentPoint)
7.3 設置bezierPath的基本屬性
bezierPath.lineWidth = 10
bezierPath.lineJoinStyle = CGLineJoin.Round
UIColor.blueColor().set()
7.4 解決剛剛點擊第一個按鈕,但是連線到CGPointZore的bug(在TouchBegin中清空舷手,在drawRect判斷)
//添加最后一個線的和最后一個
if (CGPointEqualToPoint(CGPointZero,currentPoint) == false) {
bezierPath.addLineToPoint(currentPoint)
}
8.減小觸摸button的響應范圍
現(xiàn)在的項目拧簸,你的鼠標剛剛到一個btn的邊緣,就已經(jīng)鏈接了線男窟,這樣的體驗不好盆赤,我們想去設定當手勢到了btn的圓心才連線(減小鏈接線的響應范圍)
/**
判斷是否選中的點的依據(jù)
:param: point 當前點
:returns: 所在的按鈕,可能沒有
*/
private func buttonWithPoint(point:CGPoint) -> UIButton?
{
//遍歷數(shù)組歉眷,看看是不是在9個按鈕的里面
for index in 0 ..< self.subviews.count
{
let b = self.subviews[index] as! UIButton
let wh:CGFloat = 24
let frameX = b.center.x - wh * 0.5
let frameY = b.center.y - wh * 0.5
let isIn = CGRectContainsPoint(CGRectMake(frameX, frameY, wh, wh), point)
// let isIn = CGRectContainsPoint(b.frame, point)
if isIn {
return b
}
}
return nil
}
9.拼接用戶的觸摸路徑
//拼接字符串牺六,保存用戶的觸摸路徑
private func appendCode()
{
let code = NSMutableString()
for var btn in btns {
code.appendString("\(btn.tag)")
}
print(code)
}
10.添加代理方法,讓外界知道用戶的觸摸路徑(給代理添加IBOut)
protocol GULockViewDelegate: NSObjectProtocol{
//獲取用戶的手勢密碼
func lockViewWithUserCode(lockView:GULockView,code:String)
}
添加屬性
//代理
weak var delegate:GULockViewDelegate?
11.修改連線的具體顏色
你隨意吧~