I have a simple view controller where the entire screen is an imageView. The imageView has its content mode set to UIViewContentMode.scaleAspectFill. I can draw on the picture, but as soon as I do the image shrinks horizontally. This messing up the quality of the image and I'm not sure exactly why it's happening.
I looked at this question, but I didn't understand the only answer. I think the issue is with the way I am drawing and that rect is shrinking the image.
class AnnotateViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
var lastPoint = CGPoint.zero
var fromPoint = CGPoint()
var toPoint = CGPoint.zero
var brushWidth: CGFloat = 5.0
var opacity: CGFloat = 1.0
#IBAction func startDrawing(_ sender: Any) {
self.isDrawing = !self.isDrawing
if isDrawing {
self.imageView.isUserInteractionEnabled = true
showColorButtons()
}
else {
self.imageView.isUserInteractionEnabled = false
hideColorButtons()
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
lastPoint = touch.preciseLocation(in: self.imageView)
}
}
func drawLineFrom(fromPoint: CGPoint, toPoint: CGPoint) {
UIGraphicsBeginImageContextWithOptions(self.imageView.bounds.size, false, 0.0)
imageView.image?.draw(in: CGRect(x: 0, y: 0, width: screenSize.width, height: screenSize.height))
let context = UIGraphicsGetCurrentContext()
context?.move(to: fromPoint)
context?.addLine(to: toPoint)
context?.setLineCap(CGLineCap.round)
context?.setLineWidth(brushWidth)
context?.setStrokeColor(red: 255, green: 0, blue: 0, alpha: 1.0)
context?.setBlendMode(CGBlendMode.normal)
context?.strokePath()
imageView.image = UIGraphicsGetImageFromCurrentImageContext()
imageView.alpha = opacity
UIGraphicsEndImageContext()
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let currentPoint = touch.preciseLocation(in: self.imageView)
drawLineFrom(fromPoint: lastPoint, toPoint: currentPoint)
lastPoint = currentPoint
}
}
}
UPDATE - SOLUTION
I finally got back to working on this problem. Looking at this question, I was able to use the accepted answer to solve my problem. Below is my updated code:
func drawLineFrom(fromPoint: CGPoint, toPoint: CGPoint) {
UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, true, 0.0)
let aspect = imageView.image!.size.width / imageView.image!.size.height
let rect: CGRect
if imageView.frame.width / aspect > imageView.frame.height {
let height = imageView.frame.width / aspect
rect = CGRect(x: 0, y: (imageView.frame.height - height) / 2,
width: imageView.frame.width, height: height)
} else {
let width = imageView.frame.height * aspect
rect = CGRect(x: (imageView.frame.width - width) / 2, y: 0,
width: width, height: imageView.frame.height)
}
imageView.image?.draw(in: rect)
let context = UIGraphicsGetCurrentContext()
context?.move(to: fromPoint)
context?.addLine(to: toPoint)
context?.setLineCap(CGLineCap.round)
context?.setLineWidth(brushWidth)
context?.setStrokeColor(red: self.red, green: self.green, blue: self.blue, alpha: 1.0)
context?.setBlendMode(CGBlendMode.normal)
context?.strokePath()
imageView.image = UIGraphicsGetImageFromCurrentImageContext()
annotatedImage = imageView.image
imageView.alpha = opacity
UIGraphicsEndImageContext()
}
This line:
imageView.image?.draw(in: CGRect(x: 0, y: 0, width: screenSize.width, height: screenSize.height))
Draws the image at the aspect ratio of the screen, but your image is probably not at the same aspect ratio (the ratio of width to height).
I think this might be useful: UIImage Aspect Fit when using drawInRect?
Related
I am trying to make an app that draw on screen following my finger. I took pieces from several websites and manage to get it done. screenshot app screen
The app is working , I can choose different colors and line is following my finger. Only thing is that when i use it on a real device (iphone SE and Xs Max) after a bunch of lines my app crash with the message iOS terminate on memory.
this is not happening with the simulator. I am not experienced enough to use instruments. Any idea what can cause this ?
maybe something wrong in the function :
func drawLineFrom(fromPoint: CGPoint, toPoint: CGPoint) {
Or has a iPhone just not enough memory to draw on a full screen, then I am wasting my time searching for a solution that has no solution?
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var UIImage: UIImageView!
#IBOutlet weak var tempImageView: UIImageView!
#IBOutlet weak var colourInUse: UILabel!
var lastPoint = CGPoint.zero
var red: CGFloat = 0.0
var green: CGFloat = 0.0
var blue: CGFloat = 0.0
var brushWidth: CGFloat = 2.0
var opacity: CGFloat = 1.0
var swiped = false
let colors: [(CGFloat, CGFloat, CGFloat)] = [
(0, 0, 0),
(105.0 / 255.0, 105.0 / 255.0, 105.0 / 255.0),
(1.0, 0, 0),
(0, 0, 1.0),
(51.0 / 255.0, 204.0 / 255.0, 1.0),
(102.0 / 255.0, 204.0 / 255.0, 0),
(102.0 / 255.0, 1.0, 0),
(160.0 / 255.0, 82.0 / 255.0, 45.0 / 255.0),
(1.0, 102.0 / 255.0, 0),
(1.0, 1.0, 0),
(1.0, 1.0, 1.0),
]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
UIImage.isUserInteractionEnabled = true
tempImageView.isUserInteractionEnabled = true
colourInUse.text = "Black"
}
#IBAction func pencilPressed(sender: AnyObject) {
var index = sender.tag ?? 0
if index < 0 || index >= colors.count {
index = 0
}
(red, green, blue) = colors[index]
if index == colors.count - 1 {
opacity = 1.0
}
}
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: view)
drawLineFrom(fromPoint: lastPoint, toPoint: currentPoint)
lastPoint = currentPoint
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if !swiped {
drawLineFrom(fromPoint: lastPoint, toPoint: lastPoint)
}
// Merge tempImageView into mainImageView
UIGraphicsBeginImageContext(UIImage.frame.size)
UIImage.image?.draw(in: CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height), blendMode: CGBlendMode.normal, alpha: 1.0)
tempImageView.image?.draw(in: CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height), blendMode: CGBlendMode.normal, alpha: opacity)
UIImage.image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
tempImageView.image = nil
}
func drawLineFrom(fromPoint: CGPoint, toPoint: CGPoint) {
UIGraphicsBeginImageContext(view.frame.size)
UIGraphicsBeginImageContextWithOptions(view.frame.size, false, 0)
let drawContext = UIGraphicsGetCurrentContext()
tempImageView.image?.draw(in: CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height))
drawContext?.move(to: CGPoint(x: fromPoint.x, y: fromPoint.y))
drawContext?.addLine(to: CGPoint(x: toPoint.x, y: toPoint.y))
drawContext?.setLineCap(CGLineCap.round)
drawContext?.setLineWidth(brushWidth)
drawContext?.setStrokeColor(red: red, green: green, blue: blue, alpha: 1.0)
drawContext?.setBlendMode(CGBlendMode.normal)
drawContext?.strokePath()
tempImageView.image = UIGraphicsGetImageFromCurrentImageContext()
tempImageView.alpha = opacity
UIGraphicsEndImageContext()
}
}
I'm developing simple kids alphabets drawing app in iOS. I have created bezier path of a letter with dashed line. If kids draw over the bezier path, I have to detect whether they drawing correctly or not. I'm allowing them to draw only over that path. I don't know to detect whether they are drawing correctly or not and whether they completed it correctly.
// Created layer for path
let font = UIFont.systemFont(ofSize: 350)
var unichars = [UniChar](selectedText.utf16)
var glyphs = [CGGlyph](repeating: 0, count: unichars.count)
let gotGlyphs = CTFontGetGlyphsForCharacters(font, &unichars, &glyphs, unichars.count)
if gotGlyphs {
let cgpath = CTFontCreatePathForGlyph(font, glyphs[0], nil)!
var inverse = CGAffineTransform(scaleX: 1, y: -1).translatedBy(x: 0, y: -cgpath.boundingBox.height - 1)
let path = UIBezierPath(cgPath: cgpath.copy(using: &inverse)!)
shapeLayer.path = path.cgPath
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1)
shapeLayer.lineDashPattern = [7,3]
mainImageView.layer.addSublayer(shapeLayer)
}
//Allowing user to draw only over letter path
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
guard let point = touch?.location(in: self.mainImageView) else {
return
}
if let path = shapeLayer.path, path.contains(point) {
lastPoint = point
}
}
func drawLineFrom(fromPoint: CGPoint, toPoint: CGPoint) {
// 1
UIGraphicsBeginImageContext(drawPadView.frame.size)
let context = UIGraphicsGetCurrentContext()
tempImgView.image?.draw(in: CGRect(x: 0, y: 0, width: drawPadView.frame.size.width, height: drawPadView.frame.size.height))
// 2
context?.move(to: toPoint)
context?.addLine(to: toPoint)
// 3
context?.setLineCap(.round)
context?.setLineWidth(brushWidth)
context?.setStrokeColor(UIColor(displayP3Red: red, green: green, blue: blue, alpha: 1.0).cgColor)
context?.setBlendMode(.normal)
// 4
context?.strokePath()
// 5
tempImgView.image = UIGraphicsGetImageFromCurrentImageContext()
tempImgView.alpha = opacity
UIGraphicsEndImageContext()
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
swiped = true
let touch = touches.first
guard let point = touch?.location(in: self.mainImageView) else {
return
}
if let path = shapeLayer.path, path.contains(point) {
drawLineFrom(fromPoint: lastPoint, toPoint: point)
lastPoint = point
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if !swiped {
drawLineFrom(fromPoint: lastPoint, toPoint: lastPoint)
}
// Merge tempImageView into mainImageView
UIGraphicsBeginImageContext(drawPadView.frame.size)
mainImageView.image?.draw(in: CGRect(x: 0, y: 0, width: drawPadView.frame.size.width, height: drawPadView.frame.size.height), blendMode: .normal, alpha: opacity)
tempImgView.image?.draw(in: CGRect(x: 0, y: 0, width: drawPadView.frame.size.width, height: drawPadView.frame.size.height), blendMode: .normal, alpha: opacity)
mainImageView.image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
Please suggest me how to verify user completed drawing and check whether they drawn correctly or not.
I am building an iOS drawing application and I am trying to implement a undo button to remove the line drawn by the user. I've created an array of images in a variable:
var images = [UIImage]()
Each time the user swipes and removes the finger from the screen a new image is created in the touchesEnded function. I think I need to remove the last image to get the previous image in an IBAction which will act as a undo button.
Here's a sample of my current code:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first!
guard touch.view != floaty else { return }
swiped = false
if let touch = touches.first{
postBtn.isEnabled = true
postBtn.setImage(UIImage(named: "done_icon_moved"), for: .normal)
dismissBtn.isEnabled = true
undoBtn.isEnabled = true
undoBtn.setImage(UIImage(named: "undo_icon_moved"), for: .normal)
lastPoint = touch.location(in: self.view)
}
}
func drawLineFrom(fromPoint: CGPoint, toPoint: CGPoint) {
// 1
UIGraphicsBeginImageContext(view.frame.size)
tempImageView.image?.draw(in: CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height))
let context = UIGraphicsGetCurrentContext()
// 2
context?.move(to: CGPoint(x: fromPoint.x, y: fromPoint.y))
context?.addLine(to: CGPoint(x: toPoint.x, y: toPoint.y))
// 3
context?.setBlendMode(CGBlendMode.normal)
context?.setLineCap(CGLineCap.round)
context?.setLineWidth(brushWidth)
context?.setStrokeColor(UIColor(red:red, green:green, blue:blue, alpha: 1.0).cgColor)
// 4
context?.strokePath()
// 5
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
drawLineFrom(fromPoint: lastPoint, toPoint: lastPoint)
}
// Merge tempImageView into mainImageView
UIGraphicsBeginImageContext(mainImageView.frame.size)
mainImageView.image?.draw(in: CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height),blendMode: CGBlendMode.normal, alpha: 1.0)
tempImageView.image?.draw(in: CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height), blendMode: CGBlendMode.normal, alpha: opacity)
mainImageView.image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
tempImageView.image = nil
print("touch ended")
}
Check if image array have objects or not before remove last object.
if images.count != 0 {
images.removeLast()
}
So in my swift app I'm allowing the user to paint with there finger on touch. I'm using cgcontext to do this. After the user lifts the finger and the touch ends I am dynamically adding a visualeffect view on top of the shape with the same height and width of the drawn shape.
What i want to do next is use the shape as a mask for visualeffect view. The problem right now is if i try to mask the visualeffect view with the shape. the masked view does not show unless the origin point of the shape is (0,0). Here is a link to how i'm currently attempting to implementing this (https://pastebin.com/JaM9kx4G)
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
swiped = false
if let touch = touches.first as? UITouch {
if (touch.view == sideView){
return
}
tempPath = UIBezierPath()
lastPoint = touch.location(in: view)
tempPath.move(to:lastPoint)
}
}
func drawLineFrom(fromPoint: CGPoint, toPoint: CGPoint) {
tempPath.addLine(to: CGPoint(x: toPoint.x, y: toPoint.y))
UIGraphicsBeginImageContext(view.frame.size)
let context = UIGraphicsGetCurrentContext()
otherImageView.image?.draw(in: CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height))
// 2
context!.move(to: CGPoint(x:fromPoint.x,y:fromPoint.y))
context!.addLine(to: CGPoint(x:toPoint.x, y:toPoint.y))
// 3
context!.setLineCap(CGLineCap.round)
context!.setLineWidth(brushWidth)
context!.setStrokeColor(red: red, green: green, blue: blue, alpha: 1.0)
context!.setBlendMode(CGBlendMode.normal)
// 4
context!.strokePath()
// 5
otherImageView.image = UIGraphicsGetImageFromCurrentImageContext()
otherImageView.alpha = opacity
UIGraphicsEndImageContext()
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
swiped = true
if let touch = touches.first as? UITouch {
let currentPoint = touch.location(in: view)
drawLineFrom(fromPoint: lastPoint, toPoint: currentPoint)
// 7
lastPoint = currentPoint
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
tempPath.close()
let tempImage = UIImageView(frame: CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height))
UIGraphicsBeginImageContext(tempImage.frame.size)
tempImage.image?.draw(in: CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height), blendMode: CGBlendMode.normal, alpha: 1.0)
otherImageView.image?.draw(in: CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height), blendMode: CGBlendMode.normal, alpha: opacity)
let context = UIGraphicsGetCurrentContext()
let image = UIGraphicsGetImageFromCurrentImageContext()
tempImage.image = image
otherImageView.image = nil
imageView.addSubview(tempImage)
let blur = VisualEffectView()
blur.frame = CGRect(x:tempPath.bounds.origin.x,y:tempPath.bounds.origin.y, width:tempPath.bounds.width, height:tempPath.bounds.height)
blur.blurRadius = 5
blur.layer.mask = tempImage.layer
}
How to move horizontal line in a vertical direction?
I can draw a straight horizontal line and I need to move it using long press.
How can I do that?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
{
if let touch = touches.first
{
let currentPoint = touch.location(in: self.view)
DrawLine(FromPoint: currentPoint, toPoint: currentPoint)
}
}
func DrawLine(FromPoint: CGPoint, toPoint: CGPoint)
{
UIGraphicsBeginImageContext(self.view.frame.size)
imageView.image?.draw(in: CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height))
let context = UIGraphicsGetCurrentContext()
let lineWidth : CGFloat = 5
context?.move(to: CGPoint(x: FromPoint.x, y: FromPoint.y))
context?.addLine(to: CGPoint(x: FromPoint.x + 200, y: FromPoint.y))
context?.setBlendMode(CGBlendMode.normal)
context?.setLineCap(CGLineCap.round)
context?.setLineWidth(lineWidth)
context?.setStrokeColor(UIColor(red: 0, green: 0, blue: 0, alpha: 1.0).cgColor)
context?.strokePath()
imageView.image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
you can draw a horizontal line using UIView and can move it in vertical direction using UIView.animate method by providing the required options.
In your ViewController define the following variables:
var scanlineRect = CGRect.zero
var scanlineStartY: CGFloat = 0
var scanlineStopY: CGFloat = 0
var topBottomMargin: CGFloat = 30
var scanLine: UIView = UIView()
Call drawLine method inside viewDidLoad method and call moveVertically method on your touch event:
func drawLine() {
self.addSubview(scanLine)
scanLine.backgroundColor = UIColor(red: 0.4, green: 0.8, blue: 0.4, alpha: 1.0) // green color
scanlineRect = CGRect(x: 0, y: 0, width: self.frame.width, height: 2)
scanlineStartY = topBottomMargin
scanlineStopY = self.frame.size.height - topBottomMargin
}
func moveVertically() {
scanLine.frame = scanlineRect
scanLine.center = CGPoint(x: scanLine.center.x, y: scanlineStartY)
scanLine.isHidden = false
weak var weakSelf = scanLine
UIView.animate(withDuration: 1.0, delay: 0.0, options: [.repeat, .autoreverse, .beginFromCurrentState], animations: {() -> Void in
weakSelf!.center = CGPoint(x: weakSelf!.center.x, y: scanlineStopY)
}, completion: nil)
}
what you can do is to draw this line on a UIView, and move the UIView upwards or to any position you want like so:
UIView.animate(withDuration: 0.3, animations: {
yourView.origin = CGPoint(yourView.frame.origin.x, yourYPosition)
}, completion: { (value: Bool) in
//do anything after animation is done
})