Get a pixel precise image from CGPoints - swift

I'm trying to make some kind of pencil signature view in my app, I did it with the help of this func:
func drawLine(from fromPoint: CGPoint, to toPoint: CGPoint) {
UIGraphicsBeginImageContext(canvasImageView.frame.size)
guard let context = UIGraphicsGetCurrentContext() else {
return
}
canvasImageView.image?.draw(in: canvasImageView.bounds)
context.move(to: fromPoint)
context.addLine(to: toPoint)
context.setLineCap(.round)
context.setBlendMode(.normal)
context.setLineWidth(brushWidth)
context.setStrokeColor(color.cgColor)
context.strokePath()
canvasImageView.image = UIGraphicsGetImageFromCurrentImageContext()
canvasImageView.alpha = opacity
UIGraphicsEndImageContext()
}
and I'm keeping track of the actual touches and draw the lines with those functions:
open override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else {
return
}
swiped = false
lastPoint = touch.location(in: canvasImageView)
lines.append(lastPoint)
}
open override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else {
return
}
swiped = true
let currentPoint = touch.location(in: canvasImageView)
drawLine(from: lastPoint, to: currentPoint)
lines.append(currentPoint)
lastPoint = currentPoint
}
open override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if !swiped {
drawLine(from: lastPoint, to: lastPoint)
}
lines.append(lastPoint)
UIGraphicsBeginImageContext(canvasImageView.frame.size)
canvasImageView?.image?.draw(in: canvasImageView.bounds, blendMode: .normal, alpha: opacity)
canvasImageView.image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
What happens now is that I'm able to draw on the canvas image view and export a base64 representation of the image, The problem is that the exported base64 includes the transparent image view and the signature but I need only the signature, Which means that I need the exported image size to represent only the drawn signature meaning that the pixels will differ with every different signature, now it's fixed and it equals the dimensions of the canvas image view, How I can achieve something like that?

Related

draw line using uibezierPath in a vertical way

My swift code below draws a horiziontal way the horizontal line is at a angle that does not change. Think like a x axis. I want to draw a line in the exact opposite way. Think of the y axis. The line is drawn at
bezier.addLine(to: CGPoint(x: point.x, y: startPoint!.y))
import UIKit
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
}
}
Actually you move on to startPoint and trying to add line to the same static point ... how it can add line to the same point on which you are currently residing ... add some value to Y while static X position position to add line
bezier.move(to: startPoint!)
bezier.addLine(to: CGPoint(x: startPoint!.x, y: point.y))

How to change area inside CAShapeLayer?

I have created CAShapeLayer with custom UIBezierPath and filled color. How can I change the area inside a path? Make it bigger/lower.
private var path = UIBezierPath()
private var shapeLayer = CAShapeLayer()
override public func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first as UITouch? {
let touchPoint = touch.location(in: self)
path.move(to: touchPoint)
}
}
override public func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first as UITouch? {
let touchPoint = touch.location(in: self)
path.addLine(to: touchPoint)
shapeLayer.path = path.cgPath
shapeLayer.strokeColor = strokeColor.cgColor
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.lineWidth = lineWidth
layer.addSublayer(shapeLayer)
}
}
override public func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first as UITouch? {
let touchPoint = touch.location(in: self)
path.addLine(to: touchPoint)
path.close()
shapeLayer.path = path.cgPath
shapeLayer.strokeColor = UIColor.clear.cgColor
shapeLayer.fillColor = strokeColor.cgColor
shapeLayer.lineWidth = lineWidth
layer.addSublayer(shapeLayer)
}
}
So I have a problem with changing the area. How to change the area with filled color? Or how to modify path with/or without some points?
Steps needed:
- select frame with gestures;
- fill color;
- update frame make it bigger/smaller with gestures(touches began/moved/ended)
Similar to this library https://github.com/TinyCrayon/TinyCrayon-iOS-SDK
You can't change CAShapeLayer.
But I've achieved this effect with 2 steps.
First - select an area with bezierPath, save path.
Second - Create CGContext, add image over our image with the color of bezierPath fill color, then add mask with our path and add functionality erase/unerase image.

App crash when 2 fingers are on the screen, touches began, touches moved

I'm not sure what's happening so i can't really describe it to you properly, I made an app that draws a line with the dragging of the users finger, its a sprite kit Game so i used touchesBegan and touchesMoved, so what happens is if i place a finger on the screen while I'm drawing another line the game crashes. what I'm looking for is a way to ignore the second touch until the first is over.My game Draws a line from the start position of the touch till the end position when the touches end
here is the code in my touches functions
var lineNode = SKShapeNode()
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches{
positionOfStartTouch = touch.location(in: self)
lastPoint = touch.location(in: self)
firstPoint = touch.location(in: self)
}
let pathToDraw = CGMutablePath()
print(pathToDraw.isEmpty)
pathToDraw.move(to: CGPoint(x: firstPoint.x, y: firstPoint.y))
if frame.width == 375 {
lineNode.lineWidth = 4
}else if frame.width == 414 {
lineNode.lineWidth = 6
}else if frame.width == 768 {
lineNode.lineWidth = 8
}
lineNode.strokeColor = UIColor.white
lineNode.name = "Line"
lineNode.zPosition = 100000
lineNode.path = pathToDraw
self.addChild(lineNode)
shapeNodes.append(lineNode)
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches{
positionInScene = touch.location(in: self)
}
let pathToDraw = lineNode.path as! CGMutablePath
lineNode.removeFromParent()
pathToDraw.move(to: CGPoint(x: firstPoint.x, y: firstPoint.y))
pathToDraw.addLine(to: CGPoint(x: positionInScene.x, y: positionInScene.y))
lineNode.path = pathToDraw
shapeNodes.append(lineNode)
self.addChild(lineNode)
firstPoint = positionInScene
}
The node can only have one parent. You are trying to add lineNode multiple times the the scene. Try this:
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches{
positionInScene = touch.location(in: self)
}
let pathToDraw = lineNode.path as! CGMutablePath
lineNode.removeFromParent()
pathToDraw.move(to: CGPoint(x: firstPoint.x, y: firstPoint.y))
pathToDraw.addLine(to: CGPoint(x: positionInScene.x, y: positionInScene.y))
lineNode.path = pathToDraw
if let copy = lineNode.copy() as? SKShapeNode {
shapeNodes.append(copy)
self.addChild(copy)
}
firstPoint = positionInScene
}
Do the same in touchesBegan. Of course I am not going into your logic about what should happen when multiple touches occur. I am just pointing out where is the error and why your app crashes.

strange bug with drawing in UISplitViewController

I'm trying to make screen with view in which user can draw something. I created custom view with such code:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
swiped = false
if let touch = touches.first {
lastPoint = touch.location(in: imageView)
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
swiped = true
if let touch = touches.first {
let currentPoint = touch.location(in: imageView)
drawLine(fromPoint: lastPoint, toPoint: currentPoint)
lastPoint = currentPoint
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if !swiped {
// draw a single point
drawLine(fromPoint: lastPoint, toPoint: lastPoint)
}
and drawing function
func drawLine(fromPoint: CGPoint, toPoint: CGPoint) {
UIGraphicsBeginImageContext(imageView.frame.size)
let context = UIGraphicsGetCurrentContext()
imageView.image?.draw(in: CGRect(x: 0, y: 0, width: imageView.frame.size.width, height: imageView.frame.size.height))
context?.move(to: fromPoint)
context?.addLine(to: toPoint)
context?.setLineCap(.round)
context?.setLineWidth(lineWidth)
context?.setStrokeColor(lineColor.cgColor)
context?.strokePath()
imageView.image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
when I show that view in view controller all is normal:
but when I show it in detail view in UISplitViewController, while user continue drawing, part of already drew image moving and fading out:
I can't find anything about what bug in web and have no idea what is produced such behaviour
Is anybody have any ideas about that?
That is example project where you can reproduce that bug:
https://github.com/fizzy871/DrawingBug
btw, in real project not only master view of split view controller, but navigation bar affect drawing too
Turned out that it behaves such way because of imageView frame with fractional part in size.
I just multiplied drawing context by 2 and problem solved:
func drawLine(fromPoint fromPoint: CGPoint, toPoint: CGPoint) {
// multiply to avoid problems when imageView frame value is XX.5
let fixedFrameForDrawing = CGRect(x: 0, y: 0, width: imageView.frame.size.width*2, height: imageView.frame.size.height*2)
let point1 = CGPoint(x: fromPoint.x*2, y: fromPoint.y*2)
let point2 = CGPoint(x: toPoint.x*2, y: toPoint.y*2)
UIGraphicsBeginImageContext(fixedFrameForDrawing.size)
if let context = UIGraphicsGetCurrentContext() {
imageView.image?.draw(in: fixedFrameForDrawing)
context.move(to: point1)
context.addLine(to: point2)
context.setLineCap(.round)
context.setLineWidth(lineWidth*2)
context.setStrokeColor(lineColor.cgColor)
context.strokePath()
let imageFromContext = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
imageView.image = imageFromContext
}

Bullet Firing In Direction Of Tap

So far, my app has a big ball in the middle and a small ball in the middle also. I would like to be able to tap anywhere on the screen and the small ball shoots in that direction. I've heard people say about creating vectors but I can't seem to get these working in swift 3. I am a beginner so sorry about a stupid question!
Here is my code:
var mainBall = SKSpriteNode(imageNamed: "Ball")
override func didMove(to view: SKView) {
mainBall.size = CGSize(width: 300, height: 300)
mainBall.position = CGPoint(x: frame.width / 2, y: frame.height / 2)
self.addChild(mainBall)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let position = touch.location(in: self)
print(position.x)
print(position.y)
}
for touch in (touches) {
touch.location(in: self)
let smallBall = SKSpriteNode(imageNamed: "Ball")
smallBall.position = mainBall.position
smallBall.size = CGSize(width: 100, height: 100)
smallBall.physicsBody = SKPhysicsBody(circleOfRadius: smallBall.size.width / 2)
smallBall.physicsBody?.affectedByGravity = false
self.addChild(smallBall)
}
}
You can use actions to animate SKSprideNodes:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else {
return
}
let newPosition = touch.location(in: self)
...
//assuming self.smallBall exists and is visible already:
[self.smallBall runAction:[SKAction moveTo:newPosition duration:1.0]];
}