Touches command is not working due to scroll view - swift

Hi I am new to swift programming. I wanted to created an app that can allow user to draw on an image and the drawing feature works however when I added scrollview it seems to be blocking the touch command that is suppose to be meant for the imageView is there any thing I can do to allow the user to draw on the image and keep the scrollView? the scrollView is there so that the user can zoom in and out of the image
let strokeColor:UIColor = UIColor.red
let lineWidth:CGFloat = 1.0
var path = UIBezierPath()
var shapeLayer = CAShapeLayer()
var croppedImage = UIImage()
#IBOutlet weak var scrollView: UIScrollView!
#IBOutlet weak var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
imageView.image = img
self.scrollView.minimumZoomScale = 1.0
self.scrollView.maximumZoomScale = 6.0
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return self.imageView
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first as UITouch?{
let touchPoint = touch.location(in: self.imageView)
path.move(to: touchPoint)
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first as UITouch?{
let touchPoint = touch.location(in: self.imageView)
path.addLine(to: touchPoint)
addNewPathToImage()
}
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first as UITouch?{
let touchPoint = touch.location(in: self.imageView)
path.close()
}
}
func addNewPathToImage(){
shapeLayer.path = path.cgPath
shapeLayer.strokeColor = strokeColor.cgColor
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.lineWidth = lineWidth
imageView.layer.addSublayer(shapeLayer)
}

Related

draw a straight line with a starting touch point

I want my swift code to draw a straight horiziontal line. Right now it selects a point and the user extends the line anchored in the first point. I just want the user to be able to draw the line left or right. I tried alternating bezier.addLine(to: CGPoint(x:startTouch!.x, y:startTouch!.y)) but that does not seem to have any effect.
import UIKit
class ViewController2: UIViewController {
#IBOutlet weak var drawingPlace: UIImageView!
var startTouch : CGPoint?
var secondTouch : CGPoint?
var currentContext : CGContext?
var prevImage : UIImage?
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemOrange
drawingPlace.backgroundColor = .gray
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
startTouch = touch?.location(in: drawingPlace)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches{
secondTouch = touch.location(in: drawingPlace)
if(self.currentContext == nil){
UIGraphicsBeginImageContext(drawingPlace.frame.size)
self.currentContext = UIGraphicsGetCurrentContext()
}else{
self.currentContext?.clear(CGRect(x: 0, y: 0, width: drawingPlace.frame.width, height: drawingPlace.frame.height))
}
self.prevImage?.draw(in: self.drawingPlace.bounds)
let bezier = UIBezierPath()
bezier.move(to: startTouch!)
bezier.addLine(to: secondTouch!)
bezier.addLine(to: CGPoint(x:startTouch!.x, y:startTouch!.y))
bezier.close()
UIColor.blue.set()
self.currentContext?.setLineWidth(4)
self.currentContext?.addPath(bezier.cgPath)
self.currentContext?.strokePath()
let img2 = self.currentContext?.makeImage()
drawingPlace.image = UIImage.init(cgImage: img2!)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
self.currentContext = nil
self.prevImage = self.drawingPlace.image
}
}
If you want to draw a horizontal line, create a CGPoint whose x is the location of the touch and whose y is that of the starting point. That will result in a horizontal line.
That having been said, here are a few other of observations:
If you call UIGraphicsBeginImageContext, you must call UIGraphicsEndImageContext. You should do this within touchesMoved, not trying to hang on to the context beyond this call.
If you were to do this, we would generally use UIGraphicsImageRenderer nowadays.
Personally, I wouldn't try rendering an image for every touch. That is a pretty expensive operation. I would just add a CAShapeLayer and then update the path for that layer. Let CAShapeLayer take care of the rendering of the path.
I'm not quite sure why you are iterating through the array of touches. I would just grab one and use that.
I might suggest using predictive touches to minimize perceived lagginess.
The startTouch is actually a CGPoint, not a UITouch, so I might call it startPoint instead.
If you want to make a snapshot image, I'd do that in touchesEnded, not in touchesMoved.
For example:
class ViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
var startPoint: CGPoint?
let shapeLayer: CAShapeLayer = {
let shapeLayer = CAShapeLayer()
shapeLayer.lineWidth = 4
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = UIColor.blue.cgColor
return shapeLayer
}()
override func viewDidLoad() {
super.viewDidLoad()
imageView.backgroundColor = .systemOrange
imageView.layer.addSublayer(shapeLayer)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
startPoint = touch?.location(in: imageView)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard var touch = touches.first else { return }
if let predicted = event?.predictedTouches(for: touch)?.last {
touch = predicted
}
updatePath(in: imageView, to: touch)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
updatePath(in: imageView, to: touch)
let image = UIGraphicsImageRenderer(bounds: imageView.bounds).image { _ in
imageView.drawHierarchy(in: imageView.bounds, afterScreenUpdates: true)
}
shapeLayer.path = nil
imageView.image = image
}
}
private extension ViewController {
func updatePath(in view: UIView, to touch: UITouch) {
let point = touch.location(in: view)
guard view.bounds.contains(point) else { return }
let bezier = UIBezierPath()
bezier.move(to: startPoint!)
bezier.addLine(to: CGPoint(x: point.x, y: startPoint!.y))
shapeLayer.path = bezier.cgPath
}
}
This just renders the current line as a CAShapeLayer, but when the stroke is done, it creates a snapshot of the image view (permanently capturing the stroke in an image), removes the shape layer’s path, and updates the image view’s image.
But hopefully this answers the question on how to make the line horizontal.

How to prevent back button from dissapearing?

I am creating a doodle page with an "X" as the close/dismiss button.
I also have a "clear" button to remove the doodles that have been made.
My issue is that when I press clear, even the X button disappears, how do I prevent this from happening?
This is my current app.
This is my Doodle class which allows me to draw on the view.
import UIKit
class DoodleView: UIView {
var lineColor:UIColor!
var lineWidth:CGFloat!
var path:UIBezierPath!
var touchPoint:CGPoint!
var startingPoint:CGPoint!
override func layoutSubviews() {
self.clipsToBounds = true
self.isMultipleTouchEnabled = false
lineColor = UIColor.white
lineWidth = 10
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
startingPoint = touch?.location(in: self)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
touchPoint = touch?.location(in: self)
path = UIBezierPath()
path.move(to: startingPoint)
path.addLine(to: touchPoint)
startingPoint = touchPoint
drawShapeLayer()
}
func drawShapeLayer() {
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
shapeLayer.strokeColor = lineColor.cgColor
shapeLayer.lineWidth = lineWidth
shapeLayer.fillColor = UIColor.clear.cgColor
self.layer.addSublayer(shapeLayer)
self.setNeedsDisplay()
}
func clearCanvas() {
path.removeAllPoints()
self.layer.sublayers = nil
self.setNeedsDisplay()
}
}
This is the class controlling the view controller.
import UIKit
class DoodlePageViewController: UIViewController {
#IBOutlet weak var doodleView: DoodleView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func clearDoodle(_ sender: Any) {
doodleView.clearCanvas()
}
#IBAction func CloseDoodlePage(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
}
Without really seeing how you declared the buttons, I can only guess that when you call the function clearCanvas(), the X button is part of the sublayer in doodle view since you are setting self.layer.sublayers = nil hence making it also dissapear.
Make sure that the X button is on top of the doodle view when you create it.
You are clearing all the subviews out of your view when you call self.layer.sublayers = nil
you either need to re-add the 'x' view back into the hierarchy. I am assuming using your function drawShapeLayer() is the 'x' ??
so it would be
func clearCanvas() {
path.removeAllPoints()
self.layer.sublayers = nil
drawShapeLayer()
self.setNeedsDisplay()
}

my code doesn't draw a line, it only draws a very tiny circle

My code isn't behaving the way I want it, why is the so? I don't understand what went wrong. So I'm playing around with swift, trying to transition from android to swift, I'm trying to make this simple app that draws lines, its not working as I want it, can someone please help me with what I'm doing wrong?
//
// DrawView.swift
// IOSTouch
import Foundation
import UIKit
class DrawView: UIView {
var currentLine: Line?
var finishedLines = [Line]();
//for debug
let line1 = Line(begin: CGPoint(x:50,y:50), end: CGPoint(x:100,y:100));
let line2 = Line(begin: CGPoint(x:50,y:100), end: CGPoint(x:100,y:300));
func strokeLine(line: Line){
//Use BezierPath to draw lines
let path = UIBezierPath();
path.lineWidth = 5;
path.lineCapStyle = CGLineCap.round;
path.move(to: line.begin);
path.addLine(to: line.end);
path.stroke(); //actually draw the path
}
override func draw(_ rect: CGRect) {
//draw the finished lines
UIColor.black.setStroke() //finished lines in black
for line in finishedLines{
strokeLine(line: line);
}
//for debug
strokeLine(line: line1);
strokeLine(line: line2);
//draw current line if it exists
if let line = currentLine {
UIColor.red.setStroke(); //current line in red
strokeLine(line: line);
}
}
//Override Touch Functions
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print(#function) //for debugging
let touch = touches.first!; //get first touch event and unwrap optional
let location = touch.location(in: self); //get location in view co-ordinate
currentLine = Line(begin: location, end: location);
setNeedsDisplay(); //this view needs to be updated
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
//TODO
setNeedsDisplay()
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
//TODO
setNeedsDisplay()
}
override func touchesCancelled(_ touches: Set<UITouch>?, with event: UIEvent?) {
//TODO
}
#IBInspectable var finishedLineColor: UIColor = UIColor.black {
didSet {
setNeedsDisplay()
}
}
#IBInspectable var currentLineColor: UIColor = UIColor.red {
didSet {
setNeedsDisplay()
}
}
#IBInspectable var lineThickness: CGFloat = 10 {
didSet {
setNeedsDisplay()
}
}
}
another file
import Foundation
import CoreGraphics
struct Line {
var begin = CGPoint.zero
var end = CGPoint.zero
}
This code is works, I use it:
import UIKit
class ViewController: UIViewController {
var firstPoint: CGPoint?
var secondPoint: CGPoint?
var currentLine: CAShapeLayer?
override func viewDidLoad() {
super.viewDidLoad()
}
func addLine(fromPoint start: CGPoint, toPoint end:CGPoint) {
let line = CAShapeLayer()
let linePath = UIBezierPath()
linePath.move(to: start)
linePath.addLine(to: end)
line.path = linePath.cgPath
line.strokeColor = UIColor.black.cgColor
line.lineWidth = 3
line.lineJoin = kCALineJoinRound
self.view.layer.addSublayer(line)
}
func setCurrentLine(fromPoint start: CGPoint, toPoint end: CGPoint) {
let line = CAShapeLayer()
let linePath = UIBezierPath()
linePath.move(to: start)
linePath.addLine(to: end)
line.path = linePath.cgPath
line.strokeColor = UIColor.red.cgColor
line.lineWidth = 3
line.lineJoin = kCALineJoinRound
currentLine = line
if let current = currentLine {
self.view.layer.addSublayer(current)
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
firstPoint = touches.first?.location(in: self.view)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
currentLine?.removeFromSuperlayer()
currentLine = nil
if let first = firstPoint, let current = touches.first?.location(in: self.view) {
setCurrentLine(fromPoint: first, toPoint: current)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
secondPoint = touches.first?.location(in: self.view)
if let first = firstPoint, let second = secondPoint {
addLine(fromPoint: first, toPoint: second)
currentLine?.removeFromSuperlayer()
currentLine = nil
}
}
}
It should work now
override func touchesMoved(_ touches: Set, with event: UIEvent?) {
print(#function)
let touch = touches.first!
let location = touch.location(in: self);
currentLine?.end = location;
setNeedsDisplay();
}
override func touchesEnded(_ touches: Set, with event: UIEvent?) {
print(#function) //for debugging
if var line = currentLine {
let touch = touches.first!;
let location = touch.location(in: self);
line.end = location;
finishedLines.append(line);
}
currentLine = nil;
setNeedsDisplay();
}
override func touchesCancelled(_ touches: Set<UITouch>?, with event: UIEvent?) {
print(#function) //for debugging
currentLine = nil;
setNeedsDisplay();
}

how to add color button to swift drawing app

EDIT: I added var out of class and than changed it by button in UIview.
in my class canvas:
`lineColor = color`
out of my class, but in canvas file
color = UIColor.black
in viewcontroller
#IBAction func blue(_ sender: Any) {
color = UIColor.blue
}
I'm creating a swift drawing app. I made special view for drawing(canvas) and now I created a button to change color, but it is not communicating with my canvas class in another file. I tried to add func to canvas and than call it by button, but it didn't work. Can someone help me please?
Here is my canvas.swift file
func blue(){
}
class Canvas: UIView {
var lineColor:UIColor!
var lineWidth:CGFloat!
var path:UIBezierPath!
var touchPoint:CGPoint!
var startingPoint:CGPoint!
override func layoutSubviews() {
self.clipsToBounds = true
self.isMultipleTouchEnabled = false
lineColor = UIColor.black
func blue(){
lineColor = UIColor.blue
}
lineWidth = 7
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
startingPoint = touch?.location(in: self)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
touchPoint = touch?.location(in: self)
path = UIBezierPath()
path.move(to: startingPoint)
path.addLine(to: touchPoint)
startingPoint = touchPoint
drawShapeLayer()
}
func drawShapeLayer(){
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
shapeLayer.strokeColor = lineColor.cgColor
shapeLayer.lineWidth = lineWidth
shapeLayer.fillColor = UIColor.clear.cgColor
self.layer.addSublayer(shapeLayer)
self.setNeedsDisplay()
}
func clearCanvas(){
path.removeAllPoints()
self.layer.sublayers = nil
self.setNeedsDisplay()
}
You can pass this color by NSUserDefault.
To save :
UserDefaults.standard.set(color, forKey: "colorKey")
To receive :
UserDefaults.standard.object(forKey: "colorKey")

draw a straight line in swift 3 and core graphics

I'm trying to draw a single, straight line using core graphics and swift 3 However, when touchesmoved is called, it creates multiple lines instead of just a single line. Code used is below:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var drawingPlace: UIImageView!
var startTouch : CGPoint?
var secondTouch : CGPoint?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
startTouch = touch?.location(in: drawingPlace)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches{
secondTouch = touch.location(in: drawingPlace)
UIGraphicsBeginImageContext(drawingPlace.frame.size)
drawingPlace.image?.draw(in: CGRect(x: 0, y: 0, width: drawingPlace.frame.width, height: drawingPlace.frame.height))
let bezier = UIBezierPath()
bezier.move(to: startTouch!)
bezier.addLine(to: secondTouch!)
bezier.close()
bezier.lineWidth = 4
UIColor.blue.set()
bezier.stroke()
let img = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
drawingPlace.image = img
}
}
}
btw touchesEnd creates a single line but only after the user ends the touch. I want the user to see the line being drawn as they touch.
thank you.
You need keep the current context until the line draw process has ended, after that you need keep the current image and clear the context, draw the saved image again, and draw the new line. When the touch ended is called clean the current context and save the current image, after that start the new line draw process again
this is the full code
import UIKit
class ViewController2: UIViewController {
#IBOutlet weak var drawingPlace: UIImageView!
var startTouch : CGPoint?
var secondTouch : CGPoint?
var currentContext : CGContext?
var prevImage : UIImage?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
startTouch = touch?.location(in: drawingPlace)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches{
secondTouch = touch.location(in: drawingPlace)
if(self.currentContext == nil)
{
UIGraphicsBeginImageContext(drawingPlace.frame.size)
self.currentContext = UIGraphicsGetCurrentContext()
}else{
self.currentContext?.clear(CGRect(x: 0, y: 0, width: drawingPlace.frame.width, height: drawingPlace.frame.height))
}
self.prevImage?.draw(in: self.drawingPlace.bounds)
let bezier = UIBezierPath()
bezier.move(to: startTouch!)
bezier.addLine(to: secondTouch!)
bezier.close()
UIColor.blue.set()
self.currentContext?.setLineWidth(4)
self.currentContext?.addPath(bezier.cgPath)
self.currentContext?.strokePath()
let img2 = self.currentContext?.makeImage()
drawingPlace.image = UIImage.init(cgImage: img2!)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
self.currentContext = nil
self.prevImage = self.drawingPlace.image
}
}
results