Swiftでお絵描きアプリを作成する(第1回お絵描きの実装) [UIScrollView][UIGestureRecognizer][UIBezierPath][swift2.2]
現在作成中のアプリでお絵描き部分の実装しておりまして、その記録を記事として残しておこうと思います。
計3回くらいでまとめようと思っています。
少し高機能な部分はこんな感じ!
- 拡大が可能!
- ペンの色、太さが変更可能!(次回以降)
- Redo/Undoが可能!(次回以降)
- 描いた絵の保存が可能!(次回以降)
Githubは以下です。今回はお絵描き部分のみとしています。下記の解説と合わせてご確認下さい。
▶︎GitHub - anthrgrnwrld/drawWithExpand at firstBranch
(続き書きました!)
anthrgrnwrld.hatenablog.com
UIImageを塗りつぶしてみよう [UIImage]
ふとUIImageを塗りつぶしたいと思ったのですがちょっと詰まった為、以下にメモを残しておきます。
以下の記事をがっつり参考にしました。
ソースを以下に貼っておきます。
func fillColorWithUIImage(image: UIImage) -> UIImage { let imageSize = CGSizeMake(image.size.width, image.size.height) //サイズの決定 let imageRect = CGRectMake(0, 0, imageSize.width, imageSize.height) //キャンバスのRectの決定 UIGraphicsBeginImageContextWithOptions(imageSize, false, 0.0) //コンテキスト作成(キャンバスのUIImageを作成する為) UIColor.whiteColor().setFill() //白色塗りつぶし作業1 UIRectFill(imageRect) //白色塗りつぶし作業2 image.drawInRect(imageRect) //内容を描く(真っ白) let retImage = UIGraphicsGetImageFromCurrentImageContext() //何も描かれてないUIImageを取得 UIGraphicsEndImageContext() //コンテキストを閉じる return retImage //塗りつぶしUIImageを返す }
以上です。
意外と知らない!?UIImageViewを削除する方法 [UIImageView]
UIImageViewを削除する方法...ってどうやるんだっけ?ということがあったのでメモしておきます。
どこかで以下のようにインスタンス化しているUIImageView。
let imageView = UIImageView(image: uiImage)
removeFromSuperviewを使用することで削除できます。
imageView.removeFromSuperview()
本日は以上。
extensionを使ってみる [swift2.2]
前回、タップした瞬間に円を描くサンプルを作成しました。
▶︎ swiftで円を描く [UIGestureRecognizer] [drawInRect] [Context] [swift 2.2] - MILLEN BOX
それを見た方から「extensionを使うといいよ〜」とアドバイス頂きましたのでやってみました。
今回はその方法のメモ。
Githubは以下です。
▶︎GitHub - anthrgrnwrld/oekakiGestureRecognizer at branch
あくまでextensionの使い方のメモですので、「変数をこうした方がいいぜ!」という部分はたくさん含まれていると思われます。
ソースをベタっと貼っときます
extensionUIImage.swift
import UIKit extension UIImage { func drawCircle(size: CGSize, center: CGPoint) -> UIImage { let radius: CGFloat = 20.0 //Drawする円の半径 let red: CGFloat = 1.0 //Drawする色 R let blue: CGFloat = 0.0 //Drawする色 B let green: CGFloat = 0.0 //Drawする色 G let alpha: CGFloat = 1.0 //Drawする色 Alpha UIGraphicsBeginImageContextWithOptions(size, false, 0.0) //コンテキストを取得 //タップした時に描く円のRect let circleRect = CGRectMake(center.x - radius, center.y - radius, radius * 2, radius * 2) self.drawInRect(CGRectMake(0, 0, size.width, size.height)) //コンテキストにimageViewの内容を写す let circlePath = UIBezierPath(ovalInRect: circleRect) //円を描く let color = UIColor(red:red, green:green, blue:blue, alpha:alpha) //透明色を格納 color.setFill() circlePath.fill() let retImage = UIGraphicsGetImageFromCurrentImageContext() //UIImageを取得する UIGraphicsEndImageContext() //コンテキストを閉じる return retImage //UIImageをReturnする } }
ViewController.swift
import UIKit class ViewController: UIViewController { @IBOutlet weak var imageView: UIImageView! override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. let myOekaki = OekakiGestureRecognizer(target: self, action: #selector(ViewController.drawGesture(_:))) self.view.addGestureRecognizer(myOekaki) let size = self.imageView.frame.size //コンテキストのサイズ //(extension対策)self.imageView.imageに所期画像を追加しておく UIGraphicsBeginImageContextWithOptions(size, false, 0.0) self.imageView.image?.drawInRect(CGRectMake(0, 0, size.width, size.height)) self.imageView.image = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } internal func drawGesture(sender: AnyObject) { guard let oekakiGesture = sender as? OekakiGestureRecognizer else { print("Oekaki Gesture Error happened.") return } switch oekakiGesture.state { case .Began: let size = self.imageView.frame.size //コンテキストのサイズ let touchPoint = oekakiGesture.locationInView(self.imageView) //タッチ座標を取得 //Drawを実行 -> 結果をUIImageにて取得 //let imageAfterDraw = drawCircle(self.imageView.image, size: size, center: touchPoint) let imageAfterDraw = self.imageView.image?.drawCircle(size, center: touchPoint) self.imageView.image = imageAfterDraw default: break } } }
次はprotocolのサンプルを作ってみようと思う今日この頃です。
swiftで円を描く [UIGestureRecognizer] [drawInRect] [Context] [swift 2.2]
画像イメージを貼り付け表示とかではなく、swiftで円を描く方法のメモです。
今回は前回作成した OekakiGestureRecognizer を使ってタップした瞬間に円を描くサンプルを作成しました。
参考URL: OekakiGestureRecognizer (touchDownを認識するGestureRecognizer) [UIGestureRecognizer] - MILLEN BOX
Githubは以下です。
▶︎GitHub - anthrgrnwrld/oekakiGestureRecognizer
ソースをベタっと貼り付けときます
import UIKit class ViewController: UIViewController { @IBOutlet weak var imageView: UIImageView! override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. let myOekaki = OekakiGestureRecognizer(target: self, action: #selector(ViewController.drawGesture(_:))) self.view.addGestureRecognizer(myOekaki) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } internal func drawGesture(sender: AnyObject) { guard let oekakiGesture = sender as? OekakiGestureRecognizer else { print("Oekaki Gesture Error happened.") return } switch oekakiGesture.state { case .Began: let size = self.imageView.frame.size //コンテキストのサイズ let touchPoint = oekakiGesture.locationInView(self.imageView) //タッチ座標を取得 //Drawを実行 -> 結果をUIImageにて取得 let imageAfterDraw = drawCircle(self.imageView.image, size: size, center: touchPoint) self.imageView.image = imageAfterDraw default: break } } func drawCircle(canvas: UIImage?, size: CGSize, center: CGPoint) -> UIImage { let radius: CGFloat = 20.0 //Drawする円の半径 let red: CGFloat = 1.0 //Drawする色 R let blue: CGFloat = 0.0 //Drawする色 B let green: CGFloat = 0.0 //Drawする色 G let alpha: CGFloat = 1.0 //Drawする色 Alpha UIGraphicsBeginImageContextWithOptions(size, false, 0.0) //コンテキストを取得 //タップした時に描く円のRect let circleRect = CGRectMake(center.x - radius, center.y - radius, radius * 2, radius * 2) canvas?.drawInRect(CGRectMake(0, 0, size.width, size.height)) //コンテキストにimageViewの内容を写す let circlePath = UIBezierPath(ovalInRect: circleRect) //円を描く let color = UIColor(red:red, green:green, blue:blue, alpha:alpha) //透明色を格納 color.setFill() circlePath.fill() let retImage = UIGraphicsGetImageFromCurrentImageContext() //UIImageを取得する UIGraphicsEndImageContext() //コンテキストを閉じる return retImage //UIImageをReturnする } }
こんな風に北斗七星が書けるよ!
ふとした時に役に立つかもです。
OekakiGestureRecognizer (touchDownを認識するGestureRecognizer) [UIGestureRecognizer]
現在簡単なお絵描き機能を持ったアプリを作成中ですが、その実現方法として当初UIPanGestureRecognizerを使用してタップ・タッチを検出していました。
しかしUIPanGestureRecognizerでお絵描き機能を実現した場合、画面に指を引っ付けて指を動かしたり離したりした時にタップが検出されます。
画面にタップした瞬間は検出されません。よって画面をタップして指を動かさない場合には、お絵描きの実行はされないのです。
しかし現実世界では紙に筆を落とした瞬間に墨の色が付くわけで。
今回はその実現のため、 UIGestureRecognizer を拡張した OekakiGestureRecognizer を作ってみましたのでメモしておきます。
Githubは以下です。
OekakiGestureRecognizer
import UIKit import UIKit.UIGestureRecognizerSubclass class OekakiGestureRecognizer: UIGestureRecognizer { var touchPoint: CGPoint? //タッチ座標を格納 override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent) { let numberOfTouches = event.allTouches()!.count //タッチした指の本数を取得 if numberOfTouches != 1 { self.state = .Cancelled //タッチ本数が1本以外の場合はstateをCancelに } if self.state == .Possible { let touch = touches.first! as UITouch touchPoint = touch.locationInView(self.view) //タッチ座標を取得・格納 self.state = .Began //stateをBeganに } } override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent) { let touch = touches.first! as UITouch touchPoint = touch.locationInView(self.view) //タッチ座標を取得・格納 self.state = .Changed //stateをChangedに } override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent) { let touch = touches.first! as UITouch touchPoint = touch.locationInView(self.view) //タッチ座標を取得・格納 self.state = .Ended //stateをEndedに } override func touchesCancelled(touches: Set<UITouch>, withEvent event: UIEvent) { let touch = touches.first! as UITouch touchPoint = touch.locationInView(self.view) //タッチ座標を取得・格納 self.state = .Cancelled //stateをCancelledに } override func locationInView(view: UIView?) -> CGPoint { guard var tchpnt = touchPoint else { fatalError("Can't get touchPoint") } //parameterのviewがUIScrollViewか否かで処理を分岐(拡大時対応の為) if let parameterView = self.view! as? UIScrollView { let contentSize = parameterView.contentSize let rate = self.view!.bounds.width / contentSize.width tchpnt.x = tchpnt.x * rate tchpnt.y = tchpnt.y * rate return tchpnt //parameterのviewがUIScrollViewだった時 } else { return tchpnt //parameterのviewがUIScrollView以外だった時 } } }
正直検証しきれてないので想定していない動作で何が起こるか分かってませんが、
恐らく想定していない動作については「何も起こらない」ようになっている為、大きな問題にはならないんじゃないかと楽観的に考えています。
ScrollViewのdelegate [UIScrollView]
UIScrollViewにて画像の拡大縮小に対応した時、特定の動作(拡大縮小やスクロールなど)時のみ消したいViewがある!といったことがあるかもしれません。
今回はその実現方法のメモです。
/** 写真の拡大縮小に対応 */ func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? { return self.pictureView } /** 写真の拡大縮小中に「とあるview」を隠す */ func scrollViewWillBeginZooming(scrollView: UIScrollView, withView view: UIView?) { toaruView.hidden = true } /** 写真の拡大縮小完了で「とあるview」を隠していたのを解除する */ func scrollViewDidEndZooming(scrollView: UIScrollView, withView view: UIView?, atScale scale: CGFloat) { toaruView.hidden = false } /** 写真のスクロール中に「とあるview」を隠す */ func scrollViewWillBeginDragging(scrollView: UIScrollView) { toaruView.hidden = true } /** 写真のスクロール完了で「とあるview」を隠していたのを解除する */ func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) { toaruView.hidden = false }
以上です。