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

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)

Related

CGContext & CGMutablePath drawing line But it's be reversed

I use CgContext & CGMutablePath to Draw objects but the image that drawed be reversed.
I need Draw Object to CoreMl drawing classification project.
I use Touchs events to draw object
But when I draw to top direction if will be to bottom direction.
I want the line is correct direction
I learn from link
func drawContextLine(from fromPoint: CGPoint, to toPoint:CGPoint) ->CGImage?{
let grayscale = CGColorSpaceCreateDeviceGray()
if contextDraw == nil{
contextDraw = CGContext(
data:nil, width:256, height:256, bitsPerComponent:8, bytesPerRow:0,
space:grayscale, bitmapInfo:CGImageAlphaInfo.none.rawValue)
}
// intermediate_bitmap_context?.setStrokeColor(
// red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
contextDraw?.setStrokeColor(UIColor.white.cgColor)
let path = CGMutablePath()
path.move(to: fromPoint)
path.addLine(to: toPoint)
contextDraw?.setLineWidth(5.0)
contextDraw?.beginPath()
contextDraw?.addPath(path)
contextDraw?.strokePath()
guard let imgCg = contextDraw?.makeImage() else {
return nil
}
imgViewPridict.image = UIImage.init(cgImage: imgCg)
return contextDraw?.makeImage()
}
touch begin
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first{
lastPoint = touch.location(in: self.view)
}
}
touch move
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard touches.first != nil else {
return
}
if let touch = touches.first {
let currentPoint = touch.location(in: self.view)
drawLine(from: lastPoint, to: currentPoint)
drawContextLine(from: lastPoint, to: currentPoint)
lastPoint = currentPoint
}
}
Try the code snippet below.
func drawContextLine(from fromPoint: CGPoint, to toPoint:CGPoint) ->CGImage?{
let grayscale = CGColorSpaceCreateDeviceGray()
if contextDraw == nil{
contextDraw = CGContext(
data:nil, width:256, height:256, bitsPerComponent:8, bytesPerRow:0,
space:grayscale, bitmapInfo:CGImageAlphaInfo.none.rawValue)
}
contextDraw?.setStrokeColor(UIColor.white.cgColor)
contextDraw?.saveGState()
contextDraw?.translateBy(x: 0, y: CGFloat(integerLiteral: contextDraw?.height ?? 0))
contextDraw?.scaleBy(x: 1.0, y: -1.0)
contextDraw?.setLineWidth(5.0)
contextDraw?.move(to: CGPoint(x: 100, y: 100))
contextDraw?.addLine(to: CGPoint(x: 150, y: 150))
contextDraw?.strokePath()
contextDraw?.restoreGState()
guard let imgCg = contextDraw?.makeImage() else {
return nil
}
imgViewPridict.image = UIImage.init(cgImage: imgCg)
return contextDraw?.makeImage()
}
You may need to inverse the content drawn on cgContext by applying AffineTransform to it.

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.

Drawing a line in Swift 3.x with UIBezierPath

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).

Drawing a line on view/imageview in iOS/swift3

I found a question similar to my own here:
Draw a line realtime with Swift 3.0
After piecing it together I have it all in my own program, but I am not sure where to make the call to drawLineFrom(fromPoint: CGPoint, toPoint: CGPoint) in my code so I can actually draw. The app runs, but I still can't draw. Code is below.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
swiped = false
if let touch = touches.first {
lastPoint = touch.location(in: self.view)
}
}
func drawLineFrom(fromPoint: CGPoint, toPoint: CGPoint) {
UIGraphicsBeginImageContextWithOptions(view.bounds.size, false, 0)
tempImageView.image?.draw(in: view.bounds)
let context = UIGraphicsGetCurrentContext()
context?.move(to: fromPoint)
context?.addLine(to: toPoint)
context?.setLineCap(CGLineCap.round)
context?.setLineWidth(brushWidth)
context?.setStrokeColor(red: red, green: green, blue: blue, alpha: 1.0)
context?.setBlendMode(CGBlendMode.normal)
context?.strokePath()
tempImageView.image = UIGraphicsGetImageFromCurrentImageContext()
tempImageView.alpha = opacity
UIGraphicsEndImageContext()
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
swiped = true
if let touch = touches.first {
let currentPoint = touch.location(in: view)
drawLineFrom(fromPoint: lastPoint, toPoint: currentPoint)
lastPoint = currentPoint
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if !swiped {
// draw a single point
self.drawLineFrom(fromPoint: lastPoint, toPoint: lastPoint)
}
}
let tempImageView = UIImageView()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor(r: 21, g: 221, b: 125)
//view.addSubview(titleLabel)
//view.addSubview(learnButton)
//view.addSubview(drawButton)
//view.addSubview(mainImageView)
view.addSubview(tempImageView)
//setupTitleLabel()
//setupLearnButton()
//setupDrawButton()
setupImageViews()
self.navigationController?.isNavigationBarHidden = true
}
func setupImageViews() {
tempImageView.widthAnchor.constraint(equalTo: view.widthAnchor)
tempImageView.heightAnchor.constraint(equalTo: view.heightAnchor)
}

Resources