7

enter image description here

I need to draw / doodle a line in UIImage like the picture above , I see a lot of tutorials doodle line on the UIView but not in the UIImage.

after the user doodle on the Image, I want to save it as the new image (Image that has lines). how do I do that in Swift?

i just can find draw line on UIView not in the UIImage the for drawing on UIView is like this one in this youtube tutorial http://youtube.com/watch?v=gbFHqHHApC4 , I change it to inherit DrawView to UIImageView

   struct  Stroke {
    let startPoint : CGPoint
    let endPoint : CGPoint
    let strokeColor : CGColor
}


class DrawView : UIImageView {

    var isDrawing = false
    var lastPoint : CGPoint!
    var strokeColor : CGColor! = UIColor.black.cgColor
    var strokes = [Stroke]()


    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {

        guard !isDrawing else { return }
        isDrawing = true

        guard let touch = touches.first else {return}
        let currentPoint = touch.location(in: self)
        lastPoint = currentPoint
        print(currentPoint)


    }



    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {

        guard isDrawing else { return}

        guard let touch = touches.first else {return}
        let currentPoint = touch.location(in: self)
        let stroke = Stroke(startPoint: lastPoint, endPoint: currentPoint, strokeColor: strokeColor)
        strokes.append(stroke)
        lastPoint = currentPoint

        setNeedsDisplay()


        print(currentPoint)
    }




    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {

        guard isDrawing else { return}
        isDrawing = false


        guard let touch = touches.first else {return}

        let currentPoint = touch.location(in: self)
        let stroke = Stroke(startPoint: lastPoint, endPoint: currentPoint, strokeColor: strokeColor)
        strokes.append(stroke)

        setNeedsDisplay()

        lastPoint = nil
        print(currentPoint)

    }


    override func draw(_ rect: CGRect) {

        let context = UIGraphicsGetCurrentContext()
        context?.setLineWidth(5)
        context?.setLineCap(.round)

        for stroke in strokes {
            context?.beginPath()

            context?.move(to: stroke.startPoint)
            context?.addLine(to: stroke.endPoint)

            context?.setStrokeColor(stroke.strokeColor)
            context?.strokePath()
        }

    }


    func erase() {
        strokes = []
        strokeColor = UIColor.black.cgColor
        setNeedsDisplay()
    }






}

I have assigned the UIImage in storyboard to use custom class "DrawView" like my code above, but I don't know why the lines doesn't appear on my UIImage

sarah
  • 3,819
  • 4
  • 38
  • 80

3 Answers3

10

Details

  • Xcode 10.2.1 (10E1001), Swift 5

Solution

import UIKit

// https://stackoverflow.com/a/41009006/4488252
class DrawnImageView: UIImageView {
    private lazy var path = UIBezierPath()
    private lazy var previousTouchPoint = CGPoint.zero
    private lazy var shapeLayer = CAShapeLayer()

    override func awakeFromNib() {
        super.awakeFromNib()
        setupView()
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupView()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    func setupView(){
        layer.addSublayer(shapeLayer)
        shapeLayer.lineWidth = 4
        shapeLayer.strokeColor = UIColor.white.cgColor
        isUserInteractionEnabled = true
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesBegan(touches, with: event)
        if let location = touches.first?.location(in: self) { previousTouchPoint = location }
    }

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesMoved(touches, with: event)
        if let location = touches.first?.location(in: self) {
            path.move(to: location)
            path.addLine(to: previousTouchPoint)
            previousTouchPoint = location
            shapeLayer.path = path.cgPath
        }
    }
}

// https://stackoverflow.com/a/40953026/4488252
extension UIView {
    var screenShot: UIImage?  {
        let scale = UIScreen.main.scale
        UIGraphicsBeginImageContextWithOptions(layer.frame.size, false, scale)
        if let context = UIGraphicsGetCurrentContext() {
            layer.render(in: context)
            let screenshot = UIGraphicsGetImageFromCurrentImageContext()
            UIGraphicsEndImageContext()
            return screenshot
        }
        return nil
    }
}

Usage

  1. Add DrawnImageView to your root (parent) view (drawing by touch will be enabled automatically)
  2. To save UIImage use drawingImageView.screenShot

Full sample

Do not forget to add the solution code here

import UIKit

class ViewController: UIViewController {

    fileprivate weak var savedImageView: UIImageView?
    fileprivate weak var drawnImageView: UIImageView?

    override func viewDidLoad() {
        super.viewDidLoad()

        let drawnImageView = addImageView(image: UIImage(named: "swift")) as DrawnImageView
        drawnImageView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
        drawnImageView.heightAnchor.constraint(equalToConstant: UIScreen.main.bounds.height/3).isActive = true
        self.drawnImageView = drawnImageView

        let button = UIButton()
        button.setTitle("Save Image", for: .normal)
        button.translatesAutoresizingMaskIntoConstraints = false
        button.setTitleColor(.blue, for: .normal)
        view.addSubview(button)
        button.topAnchor.constraint(equalTo: drawnImageView.bottomAnchor, constant: 60).isActive = true
        button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        button.heightAnchor.constraint(equalToConstant: 44).isActive = true
        button.addTarget(self, action: #selector(saveImageButtonTouchUpInside), for: .touchUpInside)

        let savedImageView = addImageView()
        savedImageView.topAnchor.constraint(equalTo: button.bottomAnchor, constant: 60).isActive = true
        savedImageView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
        self.savedImageView = savedImageView
    }

    private func addImageView<T: UIImageView>(image: UIImage? = nil) -> T {
        let imageView = T(frame: .zero)
        imageView.contentMode = .scaleAspectFit
        imageView.image = image
        view.addSubview(imageView)

        imageView.translatesAutoresizingMaskIntoConstraints = false
        imageView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
        imageView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
        return imageView
    }

    @objc func saveImageButtonTouchUpInside(sender: UIButton) {
        savedImageView?.image = drawnImageView?.screenShot
    }
}

Results

enter image description here enter image description here

Vasily Bodnarchuk
  • 24,482
  • 9
  • 132
  • 127
  • Nice answer!! But I save image using `UIImageWriteToSavedPhotosAlbum` in album and the picture have white borders how to fix it? – Beginnerrrrrr Nov 20 '19 at 03:09
  • @Beginnerrrrrr it is difficult to understand what happened. I suggest you to ask new question where you will share your code that simulate the problem. Because, I can not understand where can be the problem in your case. – Vasily Bodnarchuk Nov 20 '19 at 14:04
  • Awesome code, But it didn't support changing color. Like in given Example If I want to "S", "W", "I", "F" and "T" with red, green, yellow, blue and white respectively then I'm not able to do with your code. – Mrugesh Tank Sep 24 '20 at 10:14
  • @MrugeshTank you can change color `shapeLayer.strokeColor = UIColor.white.cgColor`. So, play around this line to create functionality you need – Vasily Bodnarchuk Sep 24 '20 at 13:04
  • Already tried this, but If I set red and drew "S", and then if I set Green to draw "W" then "S" also change it's color to green – Mrugesh Tank Sep 28 '20 at 04:31
3

You can put your UIImageView in background and UIView at top of it and then capture UIView as Image and merge it.

Refer this to capture UIView: How to capture UIView to UIImage without loss of quality on retina display

And then Merge it using :

func mergeImages (forgroundImage : UIImage, backgroundImage : UIImage, size : CGSize) -> UIImage {

     let bottomImage = backgroundImage
     UIGraphicsBeginImageContext(size)

     let areaSize = CGRect(x: 0, y: 0, width: size.width, height: size.height)
     bottomImage.draw(in: areaSize)

     let topImage      = forgroundImage
     topImage.draw(in: areaSize, blendMode: .normal, alpha: 1.0)

     let newImage:UIImage = UIGraphicsGetImageFromCurrentImageContext()!

     UIGraphicsEndImageContext()

   return newImage
}
Sharad Chauhan
  • 4,821
  • 2
  • 25
  • 50
0

This may help you . Refer the link

How do I draw on an image in Swift?

  1. Make an image graphics context. (Before iOS 10, you would do this by calling UIGraphicsBeginImageContextWithOptions. In iOS 10 there's another way, UIGraphicsImageRenderer, but you don't have to use it if you don't want to.)

  2. Draw (i.e. copy) the image into the context. (UIImage actually has draw... methods for this very purpose.)

  3. Draw your line into the context. (There are CGContext functions for this.)

  4. Extract the resulting image from the context. (For example, if you used UIGraphicsBeginImageContextWithOptions, you would use UIGraphicsGetImageFromCurrentImageContext.) Then close the context.

Catherine
  • 654
  • 5
  • 15