Drawing a line in Swift 3.x with UIBezierPath - ios

I am trying to draw a line on an UIView with an UIBezierpath. I think I am missing something, but wasn't able to find it out.
Here is my code:
// Code for touch recognition
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
swiped = false
if let touch = touches.first as? UITouch? {
lastPoint = (touch?.location(in: fullview))!
//print(lastPoint)
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
swiped = true
if let touch = touches.first as? UITouch? {
let currentPoint = touch?.location(in: fullview)
drawLineFrom(fromPoint: lastPoint, toPoint: currentPoint!)
lastPoint = currentPoint!
//print(lastPoint)
//print("touch moved")
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if !swiped {
drawLineFrom(fromPoint: lastPoint, toPoint: lastPoint)
}
//print("touch ended")
}
//code for drawing
func drawLineFrom(fromPoint: CGPoint, toPoint: CGPoint){
UIGraphicsBeginImageContext(fullview.frame.size)
let context = UIGraphicsGetCurrentContext()
let aPath = UIBezierPath()
//aPath.move(to: fromPoint)
//aPath.addLine(to: toPoint)
aPath.lineWidth=10.0
aPath.lineJoinStyle = .round
aPath.move(to: CGPoint(x:15,y:15))
aPath.addLine(to: CGPoint(x:80,y:80))
aPath.addClip()
aPath.close()
UIColor.green.set()
aPath.stroke()
//print("drawline")
//print("Frompoint = ",fromPoint)
//print("topoint = ",toPoint)
/* context?.setLineCap(.round)
context?.setLineWidth(brushWidth)
context?.setStrokeColor(red: red, green: green, blue: blue, alpha: 1.0)
context?.setBlendMode(.normal)
context?.beginPath()
context?.move(to: fromPoint)
context?.addLine(to: toPoint)
context?.closePath()
context?.strokePath()*/
//let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
fullview.setNeedsDisplay()
}
As you can see, I tried it also with the context, but it wasn't working too.

I use this to draw a line:
let doYourPath = UIBezierPath(rect: CGRect(x: xPos, y: yPos, width: yourWidth, height: yourHeight))
let layer = CAShapeLayer()
layer.path = doYourPath.cgPath
layer.strokeColor = UIColor.white.cgColor
layer.fillColor = UIColor.white.cgColor
self.view.layer.addSublayer(layer)
Hope this help you out. This is just one the way to draw a line in swift.

Well, you are drawing into an image context (an offscreen bitmap), not into the view. That is quite likely not what you want? Make sure you have read iOS Drawing Concepts.
Your UIView subclass should probably just track the positions of the touches (like in a var touchedPositions = [CGPoint]()) and call setNeedsDisplay().
Then implement the func draw(_ rect: CGRect) method in your subclass. Within this method, create your path and draw it according to the positions you tracked (w/o creating a new context).

Related

How to prevent multiple lines being drawn at once (swift)?

When I draw a line slowly, I believe it detects it as multiple lines instead of one, so it darkens the line too much.
I’m following this tutorial https://www.raywenderlich.com/5895-uikit-drawing-tutorial-how-to-make-a-simple-drawing-app
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else {
return
}
swiped = false
lastPoint = touch.location(in: view)
}
func drawLine(from fromPoint: CGPoint, to toPoint: CGPoint) {
//1
UIGraphicsBeginImageContext(view.frame.size)
guard let context = UIGraphicsGetCurrentContext() else {
return
}
tempImageView.image?.draw(in: view.bounds)
//2
context.move(to: fromPoint)
context.addLine(to: toPoint)
//3
context.setLineCap(.round)
context.setBlendMode(.normal)
context.setLineWidth(brushWidth)
context.setStrokeColor(color.cgColor)
context.setStrokeColor(UIColor(red: red,
green: green,
blue: blue,
alpha: opacity).cgColor)
//4
context.strokePath()
//5
tempImageView.image = UIGraphicsGetImageFromCurrentImageContext()
tempImageView.alpha = opacity
UIGraphicsEndImageContext()
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else {
return
}
//6
swiped = true
let currentPoint = touch.location(in: view)
drawLine(from: lastPoint, to: currentPoint)
//7
lastPoint = currentPoint
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
if !swiped {
//draw a single point
drawLine(from: lastPoint, to: lastPoint)
}
//merge tempImageView into mainImageView
UIGraphicsBeginImageContext(mainImageView.frame.size)
mainImageView.image?.draw(in: view.bounds, blendMode: .normal, alpha: 1.0)
tempImageView?.image?.draw(in: view.bounds, blendMode: .normal, alpha: opacity)
mainImageView.image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
tempImageView.image = nil
}
When I draw over it, it is hard to see that it darkens the point where they intersect. I would like it to be more noticeable. In this picture, opacity is set to 0.5.
The areas that have dots on the lines is where I drew really quickly. Where it is solid is where I drew the lines slowly. I would like for all lines to look like the quickly-drawn lines — minus the dots of course.
You can change the blend mode of draw line context to .copy for get the same color
From
//3
context.setBlendMode(.normal)
to
//3
context.setBlendMode(.copy)

draw freehand shapes in swift

I want to draw some shapes like someone's signature or some other freehand shapes.
I am new to UIBezierPath. I have tried following code but it didn't works as I want.
How could be it possible ?
TIA
let path = UIBezierPath()
path.move(to: from)
path.addLine(to: to)
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
shapeLayer.strokeColor = lineColor.cgColor
shapeLayer.lineWidth = 1.0
view.layer.addSublayer(shapeLayer)
Here is a basic drawing code, you should add UIImageView to paint on it, basically you need to paint a line between the previous touched point and the current point, using line cap .round
import UIKit
class BasicDrawingViewController: UIViewController {
var lastPoint = CGPoint.zero
var paintColor : UIColor = UIColor.black
var lineWidth : CGFloat = 20.0
#IBOutlet weak var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.setNavigationBarHidden(true, animated: false)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
//Draw logic
extension BasicDrawingViewController{
func drawBetweenPoints(point1:CGPoint,point2:CGPoint){
UIGraphicsBeginImageContext(self.imageView.bounds.size)
let context = UIGraphicsGetCurrentContext()
self.imageView.image?.draw(in: self.imageView.bounds)
context?.move(to: point1)
context?.addLine(to: point2)
context?.setLineCap(.round)
context?.setStrokeColor(self.paintColor.cgColor)
context?.setLineWidth(lineWidth)
context?.strokePath()
self.imageView.image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
debugPrint("Began")
if let touch = touches.first{
let point = touch.location(in: self.imageView)
self.drawBetweenPoints(point1: point, point2: point)
self.lastPoint = point
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
debugPrint("Move")
if let touch = touches.first{
let newPoint = touch.location(in: self.imageView)
self.drawBetweenPoints(point1: self.lastPoint, point2: newPoint)
self.lastPoint = newPoint
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
debugPrint("Ended")
if let touch = touches.first{
let point = touch.location(in: self.imageView)
debugPrint(point)
}
}
}
Storyboard Setup
Result
Hello #Rajinder for this you have to get touch points, so you have to use
following methods.
override func touchesBegan(_ touches: Set<UITouch>, with event:
UIEvent?)
{
let touch = event?.allTouches?.first
let touchLocation: CGPoint? = touch?.location(in: self.view)
//---Declare "from" globally as CGPoint
from = CGPoint(x: (touchLocation?.x)!, y: (touchLocation?.y)!)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = event?.allTouches?.first
let touchLocation: CGPoint? = touch?.location(in: self.view)
//--Get New touch point
let to = CGPoint(x: (touchLocation?.x)!, y: (touchLocation?.y)!)
//--Draw line
drawLineFromPoint(from: from, to: to, ofColor: UIColor.red, inView: self.view)
//--Save as older point
from = to
}
func drawLineFromPoint(from : CGPoint, to:CGPoint, ofColor lineColor: UIColor, inView view:UIView)
{
//design the path
let path = UIBezierPath()
path.move(to: from)
path.addLine(to: to)
//design path in layer
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
shapeLayer.strokeColor = lineColor.cgColor
shapeLayer.lineWidth = 1.0
view.layer.addSublayer(shapeLayer)
}

Drawing on UIImage and save original size

I need to draw lines on UIImage (image load from file system) and save it, but I have a problem. When I trying to save context to file, saved image have size equal imageView size (If I set "tempImageView.frame.size" to UIGraphicsBeginImageContextWithOptions and draw(in:)). If I change "size" in code and set tempImageView.image?.size saved image size is OK, but drawing is not work good (lines draw with offset, and lines view badly)
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.swiped = false
if let touch = touches.first {
lastPoint = touch.location(in: self.view)
print(lastPoint)
}
}
func drawLineFrom(fromPoint: CGPoint, toPoint: CGPoint) {
let scale = UIScreen.main.scale
UIGraphicsBeginImageContextWithOptions((tempImageView.image?.size)!, false, scale)
let context = UIGraphicsGetCurrentContext()
tempImageView.image?.draw(in: CGRect(origin: CGPoint.zero, size: (tempImageView.image?.size)!))
//let context = UIGraphicsGetCurrentContext()
print("context size = \(context)")
print("fromPoint = \(fromPoint), toPoint = \(toPoint)")
context?.move(to: CGPoint(x: fromPoint.x, y:fromPoint.y))
context?.addLine(to: CGPoint(x: toPoint.x, y:toPoint.y))
context?.setBlendMode(CGBlendMode.normal)
context?.setLineCap(CGLineCap.round)
context?.setLineWidth(5)
context?.setStrokeColor(UIColor(red: 0, green: 0, blue: 0, alpha: 1).cgColor)
context?.strokePath()
tempImageView.image = UIGraphicsGetImageFromCurrentImageContext()
self.newImage = UIImagePNGRepresentation(UIGraphicsGetImageFromCurrentImageContext()!)
UIGraphicsEndImageContext()
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
swiped = true
if let touch = touches.first {
let currentPoint = touch.location(in: self.view)
drawLineFrom(fromPoint: lastPoint, toPoint: currentPoint)
lastPoint = currentPoint
print(lastPoint)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if !swiped {
drawLineFrom(fromPoint: lastPoint, toPoint: lastPoint)
}
}

Drawing a rectangle on UIImageView using Core Graphics

I want to let the User to draw a rectangle on UIImageView
I added two variables for first an last touch locations
I added this function:-
func draw(from: CGPoint, to: CGPoint) {
UIGraphicsBeginImageContext(imageView.frame.size)
context = UIGraphicsGetCurrentContext()
context?.setStrokeColor(UIColor(red: 0, green: 0, blue: 0, alpha: 1.0).cgColor)
context?.setLineWidth(5.0)
let currentRect = CGRect(x: from.x,
y: from.y,
width: to.x - from.x,
height: to.y - from.y)
context?.addRect(currentRect)
context?.drawPath(using: .stroke)
context?.strokePath()
imageView.image?.draw(in: self.imageView.frame)
imageView.image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
I add the method to (touchMoved) it draws many rectangles
I add the method to (touchEnded) it draws one, but it does not appear when the user move the touch
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
firstTouchLocation = touch.location(in: self.view)
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
lastTouchLocation = touch.location(in: self.view)
draw(from: firstTouchLocation, to: lastTouchLocation)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
lastTouchLocation = touch.location(in: self.view)
draw(from: firstTouchLocation, to: lastTouchLocation)
}
}
I want to let the user extend the rectangle when touchMoved and draw when the touchEnded.
You are replacing your image with a new image composed of the previous image plus a rectangle drawn over it. Rather than drawing the image from the image view, draw the original image.
Alternatively, you could render the the rectangle as a shape layer and just update that shape layer's path:
class ViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
private let shapeLayer: CAShapeLayer = {
let _shapeLayer = CAShapeLayer()
_shapeLayer.fillColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 0).cgColor
_shapeLayer.strokeColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1).cgColor
_shapeLayer.lineWidth = 3
return _shapeLayer
}()
private var startPoint: CGPoint!
override func viewDidLoad() {
super.viewDidLoad()
imageView.layer.addSublayer(shapeLayer)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
startPoint = touches.first?.location(in: imageView)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let startPoint = startPoint, let touch = touches.first else { return }
let point: CGPoint
if let predictedTouch = event?.predictedTouches(for: touch)?.last {
point = predictedTouch.location(in: imageView)
} else {
point = touch.location(in: imageView)
}
updatePath(from: startPoint, to: point)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let startPoint = startPoint, let touch = touches.first else { return }
let point = touch.location(in: imageView)
updatePath(from: startPoint, to: point)
imageView.image = imageView.snapshot(afterScreenUpdates: true)
shapeLayer.path = nil
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
shapeLayer.path = nil
}
private func updatePath(from startPoint: CGPoint, to point: CGPoint) {
let size = CGSize(width: point.x - startPoint.x, height: point.y - startPoint.y)
let rect = CGRect(origin: startPoint, size: size)
shapeLayer.path = UIBezierPath(rect: rect).cgPath
}
}
Where:
extension UIView {
func snapshot(afterScreenUpdates: Bool = false) -> UIImage {
UIGraphicsBeginImageContextWithOptions(bounds.size, isOpaque, 0)
drawHierarchy(in: bounds, afterScreenUpdates: afterScreenUpdates)
let image = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return image
}
}
This is not only simpler, but more efficient, too.
That yields:
By the way, I might suggest using predictive touches in touchesMoved. On a device (not the simulator) that can yield a slightly more responsive UI.

Why when I draw something it doesn't appear where my finger was moving?

The line appears a couple inches away from where I wanted it to be drawn. Im making a drawing app in SpriteKit and I try to draw something on the bottom of my UIImageView but it appears couple inches away from where my finger was. Why does that happen?
class GameScene: SKScene {
var drawImageView: UIImageView! = UIImageView()
var lastPoint = CGPoint.zero
var swiped = false
override func didMove(to view: SKView) {
drawImageView.frame = previewCamera.bounds
previewCamera.addSubview(drawImageView)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
swiped = false
if let touch = touches.first {
lastPoint = touch.location(in: self.view)
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
swiped = true
if let touch = touches.first {
let currentPoint = touch.location(in: self.view)
draw(fromPoint: lastPoint, toPoint: currentPoint)
lastPoint = currentPoint
}
}
func draw(fromPoint:CGPoint,toPoint:CGPoint) {
UIGraphicsBeginImageContext((self.view?.frame.size)!)
drawImageView.image?.draw(in: CGRect(x: 0, y: 0, width: (self.view?.frame.width)!, height: (self.view?.frame.height)!))
let context = UIGraphicsGetCurrentContext()
context?.move(to: CGPoint(x: fromPoint.x, y: fromPoint.y))
context?.addLine(to: CGPoint(x: toPoint.x, y: toPoint.y))
context?.setBlendMode(CGBlendMode.normal)
context?.setLineCap(CGLineCap.round)
context?.setLineWidth(7)
context?.setStrokeColor(UIColor(red: red, green: green, blue: blue, alpha: 1.0).cgColor)
context?.strokePath()
drawImageView.image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if !swiped {
draw(fromPoint: lastPoint, toPoint: lastPoint)
}
}
}

Resources