I am working on a drawing app where the user draws something on a view. On the same view controller I would like to create a button that would erase everything that was on the view and give it a blank slate. How would I do this? I added all of my code that is attached to the uiview to draw the object.
import uikit
class ViewController: UIViewController {
#IBOutlet weak var jj: draw!
#IBAction func enterScore(_ sender: Any) {
(view as? drawViewSwift)?.clear()
}}
import UIKit
struct stroke {
let startPoint: CGPoint
let endPoint: CGPoint
let color: CGColor
}
class drawViewSwift: subClassOFUIVIEW {
var isDrawing = false
var lastPoint : CGPoint!
var strokeColor : CGColor = UIColor.black.cgColor
var storkes = [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
setNeedsDisplay()
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard isDrawing else {return}
guard let touch = touches.first else {return}
setNeedsDisplay()
let currentPoint = touch.location(in: self)
print(currentPoint)
let sstroke = stroke(startPoint: lastPoint, endPoint: currentPoint, color: strokeColor)
storkes.append(sstroke)
lastPoint = 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 sstroke = stroke(startPoint: lastPoint, endPoint: currentPoint, color: strokeColor)
storkes.append(sstroke)
lastPoint = nil
setNeedsDisplay()
print(currentPoint)
}
private var clearing = false
func clear() {
clearing = true
setNeedsDisplay()
}
override func draw(_ rect: CGRect) {
super.draw(rect)
defer { setNeedsDisplay() }
let context = UIGraphicsGetCurrentContext()
guard !clearing else {
context?.clear(bounds)
context?.setFillColor(backgroundColor?.cgColor ?? UIColor.clear.cgColor)
context?.fill(bounds)
storkes.removeAll()
clearing = false
return
}
context?.setLineWidth(10)
context?.setLineCap(.round)
for stroke in storkes {
context?.beginPath()
context?.move(to:stroke.startPoint)
context?.addLine(to: stroke.endPoint)
context?.setStrokeColor(stroke.color)
context?.strokePath()
}
}
func crase() {
storkes = []
strokeColor = UIColor.black.cgColor
setNeedsDisplay()
}
}
class subClassOFUIVIEW: UIView {
override func awakeFromNib() {
layer.shadowOpacity = 1
layer.shadowOffset = CGSize(width: 1, height: 1)
layer.shadowPath = CGPath(rect: bounds, transform: nil)
layer.shadowPath = CGPath(rect: bounds, transform: nil)
}
}
class MyViewController : UIViewController {
override func loadView() {
let view = drawViewSwift()
view.backgroundColor = .white
let button = UIButton()
button.frame = CGRect(x: 150, y: 200, width: 200, height: 20)
button.setTitle("Clear", for: .normal)
button.setTitleColor(.blue, for: .normal)
button.addTarget(self, action: #selector(clear), for: .touchUpInside)
view.addSubview(button)
self.view = view
}
#objc func clear() {
(view as? drawViewSwift)?.clear()
}
}
You can add to your drawViewSwift class this code:
private var clearing = false
func clear() {
clearing = true
setNeedsDisplay()
}
At this point your drawRect should change to clear the view:
override func draw(_ rect: CGRect) {
super.draw(rect)
defer { setNeedsDisplay() }
let context = UIGraphicsGetCurrentContext()
guard !clearing else {
context?.clear(bounds)
context?.setFillColor(backgroundColor?.cgColor ?? UIColor.clear.cgColor)
context?.fill(bounds)
storkes.removeAll()
clearing = false
return
}
context?.setLineWidth(10)
context?.setLineCap(.round)
for stroke in storkes {
context?.beginPath()
context?.move(to:stroke.startPoint)
context?.addLine(to: stroke.endPoint)
context?.setStrokeColor(stroke.color)
context?.strokePath()
}
}
In the guard we will call the clerk method of the context which will reset the context to be at its initial state. This method will cause the context to redraw a transparent view. Since the view is now transparent we must redraw our canvas to be with a background color again. We do it by using the fill method after setting the fill color to be the background color of the view.
At this point all it's remaining to do is to clear our strokes array and switch back the value of clearing to false so that we are ready to draw again.
In your view controller, the clear button just have to call the clear method of this view.
Try it in playground:
//: A UIKit based Playground for presenting user interface
import UIKit
import PlaygroundSupport
struct stroke {
let startPoint: CGPoint
let endPoint: CGPoint
let color: CGColor
}
class drawViewSwift: subClassOFUIVIEW {
var isDrawing = false
var lastPoint : CGPoint!
var strokeColor : CGColor = UIColor.black.cgColor
var storkes = [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
setNeedsDisplay()
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard isDrawing else {return}
guard let touch = touches.first else {return}
setNeedsDisplay()
let currentPoint = touch.location(in: self)
print(currentPoint)
let sstroke = stroke(startPoint: lastPoint, endPoint: currentPoint, color: strokeColor)
storkes.append(sstroke)
lastPoint = 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 sstroke = stroke(startPoint: lastPoint, endPoint: currentPoint, color: strokeColor)
storkes.append(sstroke)
lastPoint = nil
setNeedsDisplay()
print(currentPoint)
}
private var clearing = false
func clear() {
clearing = true
setNeedsDisplay()
}
override func draw(_ rect: CGRect) {
super.draw(rect)
defer { setNeedsDisplay() }
let context = UIGraphicsGetCurrentContext()
guard !clearing else {
context?.clear(bounds)
context?.setFillColor(backgroundColor?.cgColor ?? UIColor.clear.cgColor)
context?.fill(bounds)
storkes.removeAll()
clearing = false
return
}
context?.setLineWidth(10)
context?.setLineCap(.round)
for stroke in storkes {
context?.beginPath()
context?.move(to:stroke.startPoint)
context?.addLine(to: stroke.endPoint)
context?.setStrokeColor(stroke.color)
context?.strokePath()
}
}
func crase() {
storkes = []
strokeColor = UIColor.black.cgColor
setNeedsDisplay()
}
}
class subClassOFUIVIEW: UIView {
override func awakeFromNib() {
layer.shadowOpacity = 1
layer.shadowOffset = CGSize(width: 1, height: 1)
layer.shadowPath = CGPath(rect: bounds, transform: nil)
layer.shadowPath = CGPath(rect: bounds, transform: nil)
}
}
class MyViewController : UIViewController {
override func loadView() {
let view = drawViewSwift()
view.backgroundColor = .white
let button = UIButton()
button.frame = CGRect(x: 150, y: 200, width: 200, height: 20)
button.setTitle("Clear", for: .normal)
button.setTitleColor(.blue, for: .normal)
button.addTarget(self, action: #selector(clear), for: .touchUpInside)
view.addSubview(button)
self.view = view
}
#objc func clear() {
(view as? drawViewSwift)?.clear()
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
Your code to draw and to clear works. You should correct your viewController code and the layout structure you have choose. I think it's better to make a view dedicated to draw, so for example you could have 3 different objects to the main view, one for your label, another for your button and the last as your drawViewSwift. With this structure your main view can handle the constraints between objects and the other objects don't disturb the drawing (and obviusly the cleaning..):
Layout:
Output:
If number of strikes are high - Recommanded
If your strokes array is too big. I recommend to use below code
let rectangle = CGRect(x: 0, y: 0, width: self.height, height: self.height)
CGContextSetFillColorWithColor(context, UIColor.white.CGColor)
CGContextAddRect(context, rectangle)
CGContextDrawPath(context, .Fill)
If user hasn't drawn many strikes
Since you have all the current drawn strokes inside array strokes. below method will also work.
Just redraw above thus strokes with your view background color
override func clearView() {
let context = UIGraphicsGetCurrentContext()
context?.setLineWidth(10)
context?.setLineCap(.round)
for stroke in storkes {
context?.beginPath()
context?.move(to:stroke.startPoint)
context?.addLine(to: stroke.endPoint)
context?.setStrokeColor(UIColor.white.cgColor)
// Assuming your background color is white
context?.strokePath()
}
}
Related
Hello I'm trying to draw some rectangles on a UIImageView but when I add the background image the rectangle drawing is not working anymore:
This is the code:
import UIKit
class DrawView: UIView {
var startPos: CGPoint?
var endPos: CGPoint?
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else {
return
}
if startPos != nil && endPos != nil {
print("both there")
drawRectangle(context: context, startPoint: startPos!, endPoint: endPos!, isFilled: false)
}
}
public func drawRectangle(context: CGContext, startPoint: CGPoint, endPoint: CGPoint, isFilled: Bool) {
let xx = startPoint
let xy = CGPoint(x:endPoint.x, y:startPoint.y)
let yx = CGPoint(x:startPoint.x, y:endPoint.y)
let yy = endPoint
print("draw rectangle")
context.move(to: xx)
context.addLine(to: xx)
context.addLine(to: xy)
context.addLine(to: yy)
context.addLine(to: yx)
context.addLine(to: xx)
context.setLineCap(.square)
context.setLineWidth(8)
if isFilled {
context.setFillColor(UIColor.purple.cgColor)
context.fillPath()
} else {
context.setStrokeColor(UIColor.red.cgColor)
context.strokePath()
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let position = touch.location(in: self)
print(position)
startPos = position
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let position = touch.location(in: self)
print(position)
endPos = position
setNeedsDisplay()
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let position = touch.location(in: self)
print(position)
endPos = position
setNeedsDisplay()
}
}
func addBackground() {
// screen width and height:
let width = UIScreen.main.bounds.size.width
let height = UIScreen.main.bounds.size.height
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: width, height: height))
imageView.image = UIImage(named: "img1.jpg")
// you can change the content mode:
imageView.contentMode = UIView.ContentMode.scaleAspectFill
self.addSubview(imageView)
self.sendSubviewToBack(imageView)
}
}
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var drawView: DrawView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
drawView.addBackground()
}
}
Im suspecting that I'm not using the correct UIGraphicsGetCurrentContext after setting the image but I was not able to figure it out how to fix it.
I'm working on a drawing app prototype and what I want to achieve is perfect rounding lines. I've implemented drawing via curves but still, my lines look a little bit pixelated.
Here is my result:
and here is what I'm trying to achieve:
class ViewController: UIViewController {
private var currentPoint: CGPoint?
private var previousPoint1: CGPoint?
private var previousPoint2: CGPoint?
private var lineColor = UIColor.black
private var lineWidth = CGFloat(10)
private var lineAlpha = CGFloat(1)
#IBOutlet private weak var imageView: UIImageView!
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
previousPoint1 = touch.previousLocation(in: self.view)
currentPoint = touch.location(in: self.view)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
previousPoint2 = previousPoint1
previousPoint1 = touch.previousLocation(in: self.view)
currentPoint = touch.location(in: self.view)
let mid1 = middlePoint(previousPoint1!, previousPoint2: previousPoint2!)
let mid2 = middlePoint(currentPoint!, previousPoint2: previousPoint1!)
draw(move: CGPoint(x: mid1.x, y: mid1.y), to: CGPoint(x: mid2.x, y: mid2.y), control: CGPoint(x: previousPoint1!.x, y: previousPoint1!.y))
}
public override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
touchesMoved(touches, with: event)
}
private func middlePoint(_ previousPoint1: CGPoint, previousPoint2: CGPoint) -> CGPoint {
return CGPoint(x: (previousPoint1.x + previousPoint2.x) * 0.5, y: (previousPoint1.y + previousPoint2.y) * 0.5)
}
func draw(move: CGPoint, to: CGPoint, control: CGPoint) {
UIGraphicsBeginImageContext(view.frame.size)
guard let ctx = UIGraphicsGetCurrentContext() else { return }
imageView.image?.draw(in: view.bounds)
ctx.move(to: move)
ctx.addQuadCurve(to: to, control: control)
ctx.setLineCap(.round)
ctx.setLineJoin(.round)
ctx.setLineWidth(lineWidth)
ctx.setStrokeColor(lineColor.cgColor)
ctx.setBlendMode(.normal)
ctx.setAlpha(lineAlpha)
ctx.strokePath()
let finalImage = UIGraphicsGetImageFromCurrentImageContext()
imageView.image = finalImage
UIGraphicsEndImageContext()
}
}
P.S. I want to draw via context, not using drawRect
The problem here is that the scale of imageContext you are creating is not correct. Rather than creating your context with UIGraphicsBeginImageContext try to create it with UIGraphicsBeginImageContextWithOptions and give scale value as 0. When you specify a scale value of 0, the scale factor is set to the scale factor of the device’s main screen. Updated code snippet would be like the one follows.
func draw(move: CGPoint, to: CGPoint, control: CGPoint) {
UIGraphicsBeginImageContextWithOptions(view.frame.size, false, 0)
guard let ctx = UIGraphicsGetCurrentContext() else { return }
imageView.image?.draw(in: view.bounds)
ctx.move(to: move)
ctx.addQuadCurve(to: to, control: control)
ctx.setLineCap(.round)
ctx.setLineJoin(.round)
ctx.setLineWidth(lineWidth)
ctx.setStrokeColor(lineColor.cgColor)
ctx.setBlendMode(.normal)
ctx.setAlpha(lineAlpha)
ctx.strokePath()
let finalImage = UIGraphicsGetImageFromCurrentImageContext()
imageView.image = finalImage
UIGraphicsEndImageContext()
}
Using Swift to draw lines, it keeps moving up the longer I continue to draw. Or maybe it's rotating back, like it's on a ball? The pictures show me starting out with a line, then as I draw, you can see that it reaches the top of the square. It's so weird! Why is that happening? How can I stop it? Code below.
var context: CGContext?
var adjustedImageViewRect: CGRect?
var adjustedImageView: UIImageView?
var lastPoint = CGPoint.zero
var firstPoint = CGPoint.zero
var brushWidth: CGFloat = 10.0
var opacity: CGFloat = 1.0
var swiped = false
var undermineArray: [CGPoint] = []
#IBOutlet weak var drawLineImageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
drawLineImageView.image = UIImage(named: "layer")
}
override func viewDidAppear(_ animated: Bool) {
let rect = AVMakeRect(aspectRatio: drawLineImageView.image!.size, insideRect: drawLineImageView.bounds)
adjustedImageViewRect = rect
adjustedImageView = UIImageView(frame: rect)
self.view.addSubview(adjustedImageView!)
self.view.bringSubview(toFront: adjustedImageView!)
}
func drawLine(from fromPoint: CGPoint, to toPoint: CGPoint) {
if let adjImgView = adjustedImageView {
if let adjImgViewRect = adjustedImageViewRect {
UIGraphicsBeginImageContextWithOptions(adjImgViewRect.size, false, 0)
adjImgView.image?.draw(in: adjImgView.bounds)
context = UIGraphicsGetCurrentContext()
context?.move(to: fromPoint)
context?.addLine(to: toPoint)
context?.setLineCap(CGLineCap.round)
context?.setLineWidth(brushWidth)
context?.setStrokeColor(UIColor.red.cgColor)
context?.setBlendMode(CGBlendMode.normal)
context?.strokePath()
adjImgView.image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
//clear out previous line
firstPoint = CGPoint.zero
lastPoint = CGPoint.zero
undermineArray.removeAll()
adjustedImageView?.image = UIImage()
swiped = false
if let touch = touches.first {
if let rect = adjustedImageViewRect {
if (touch.location(in: self.adjustedImageView).y < 0
|| touch.location(in: self.adjustedImageView).y > rect.height) {
adjustedImageView?.image = UIImage()
return
}
}
firstPoint = touch.location(in: self.adjustedImageView)
lastPoint = touch.location(in: self.adjustedImageView)
undermineArray.append(firstPoint)
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
swiped = true
if let touch = touches.first {
var currentPoint = touch.location(in: adjustedImageView)
if let rect = adjustedImageViewRect {
if (touch.location(in: self.adjustedImageView).y < 0) {
currentPoint.y = 0
}
if (touch.location(in: self.adjustedImageView).y > rect.height) {
currentPoint.y = rect.height
}
}
undermineArray.append(currentPoint)
drawLine(from: lastPoint, to: currentPoint)
lastPoint = currentPoint
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if let rect = adjustedImageViewRect {
if (firstPoint.y < 0 || firstPoint.y > rect.height) {
adjustedImageView?.image = UIImage()
return
}
}
if let touch = touches.first {
if let rect = adjustedImageViewRect {
if (touch.location(in: self.adjustedImageView).y < 0) {
lastPoint.y = 0
}
if (touch.location(in: self.adjustedImageView).y > rect.height) {
lastPoint.y = rect.height
}
}
}
if !swiped {
self.drawLine(from: lastPoint, to: lastPoint)
}
}
AH I got it. It turns out that UIImage's draw() function draws the image, and unbeknownst to me until now, "scales it as needed to fit."
So, we'll use something else. We'll just use drawAsPattern().
Change
adjImgView.image?.draw(in: adjImgView.bounds)
to
adjImgView.image?.drawAsPattern(in: adjImgView.bounds)
and voilà, no crazy scaling/spinning/shrinking going on.
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
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
Add DrawnImageView to your root (parent) view (drawing by touch will be enabled automatically)
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
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
}
This may help you . Refer the link
How do I draw on an image in Swift?
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.)
Draw (i.e. copy) the image into the context. (UIImage actually has draw... methods for this very purpose.)
Draw your line into the context. (There are CGContext functions for this.)
Extract the resulting image from the context. (For example, if you used UIGraphicsBeginImageContextWithOptions, you would use UIGraphicsGetImageFromCurrentImageContext.) Then close the context.
i have searched the solution for this case, but i didn't find it on the internet. i want to draw/doodle a line like snapchat or whatsapp status that allow user to draw freely. I found a similar tutorial but it just drew on the UIView in this tutorial : https://www.youtube.com/watch?v=gbFHqHHApC4&t=7s
i want to allow user to freely draw on the image they pick from image gallery/camera, and then save the result back as the UIImage
the source code of the doodle on the UIView (the first picture) is below
first, make the custom view
class DrawView : ShadowView {
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() // ditampilkan ke layar
}
}
The struct of the stoke is like this one :
struct Stroke {
let startPoint : CGPoint
let endPoint : CGPoint
let strokeColor : CGColor
}
after that, assign the custom view to ViewController :
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var drawView: DrawView!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func yellowDidPressed(_ sender: Any) {
drawView.strokeColor = UIColor.yellow.cgColor
}
#IBAction func redDidPressed(_ sender: Any) {
drawView.strokeColor = UIColor.red.cgColor
}
#IBAction func blueDidPressed(_ sender: Any) {
drawView.strokeColor = UIColor.blue.cgColor
}
#IBAction func erasedDidPressed(_ sender: Any) {
drawView.erase()
}
}
so how if i want to apply that custom view on the UIImageView and save the result as the new UIImage? I am new in programming actually, really needyour help. Thanks :)