Swift change layer colour - ios

Currently in my app when user tap on the screen a red dot will appear at wherever the user touch if a user tap on the screen multiple times there will be multiple dot. I want to add a feature to allow user to change the colour.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first as UITouch?{
let location = (touch as! UITouch).location(in: self.imageView)
let layer = CAShapeLayer()
layer.path = UIBezierPath(roundedRect: CGRect(x: location.x, y: location.y, width: 2, height: 2), cornerRadius: 50).cgPath
layer.fillColor = UIColor.red.cgColor
imageView.layer.addSublayer(layer)
}
}
When I tried the following method I am able to change the colour but for some reason no matter how many times I've tap on the screen there will only be one dot. My guess is that every time I tap on my screen a new layer will be created and it will replace the old layer. is there anyway I can allow user to change the colour of the layer while keeping all of dots that is already on the layer?
class ViewController: UIViewController,UIScrollViewDelegate {
let layer = CAShapeLayer()
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first as UITouch?{
let location = (touch as! UITouch).location(in: self.imageView)
layer.path = UIBezierPath(roundedRect: CGRect(x: location.x, y: location.y, width: 2, height: 2), cornerRadius: 50).cgPath
layer.fillColor = UIColor.red.cgColor
imageView.layer.addSublayer(layer)
}
}
#IBAction func changecolorBtn(_ sender: Any) {
layer.fillColor = UIColor.green.cgColor
}
}

Because of you used only one layer
let layer = CAShapeLayer()
and whenever you set
layer.path = UIBezierPath(roundedRect: CGRect(x: location.x, y: location.y, width: 2, height: 2), cornerRadius: 50).cgPath
It will remove the old path, and your new path has only one point.
That's why
When I tried the following method I am able to change the colour but for some reason no matter how many times I've tap on the screen there will only be one dot.
You can simple fix you issue by combine all your dot path. Like this
// add the combine path as your global variable
let layer = CAShapeLayer()
let combinePath = CGMutablePath()
and change your add path code
layer.path = UIBezierPath(roundedRect: CGRect(x: location.x, y: location.y, width: 2, height: 2), cornerRadius: 50).cgPath
to add the combine path
let path = UIBezierPath(roundedRect: CGRect(x: location.x, y: location.y, width: 2, height: 2), cornerRadius: 50).cgPath
combinePath.addPath(path)
layer.path = combinePath

Related

Draw a cross in a position and how node rotation works

I'm playing with spriteKit to create a little game and I'm getting blocked trying to draw a cross (like and X) at a point where i've touched the screen. As i want to draw an X I thought in rotate two rectangles, one Pi/4 and the other one -Pi/4.
I've the following code, i've been playing with node position adding 10 or 20px on both axis. But I cannot understand how the rects intersect on their center for create a cross (like an X).
That's the code I have :
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
{
guard let touch = touches.first
else
{
return
}
let location = touch.location(in: self)
let touchedNode = nodes(at: location)
_ = atPoint(location).name
print("PosiciĆ³n touch: \(touchedNode)")
// Draw the cross in the touched point
let redSprite1 = SKShapeNode(rect: CGRect(x: 0, y: 0, width: 10, height: 40))
redSprite1.fillColor = .green
redSprite1.position = CGPoint(x: location.x - 10.0, y: location.y)
redSprite1.zRotation = CGFloat(-Pi/4)
addChild(redSprite1)
// 2
let redSprite2 = SKShapeNode(rect: CGRect(x: 0, y: 0, width: 10, height: 40))
redSprite2.fillColor = .red
redSprite2.position = CGPoint(x: location.x + 10.0, y: location.y)
redSprite2.zRotation = CGFloat(Pi/4)
addChild(redSprite2)
}
I've noted that the rotation is made not from the center of the shape but in their bottom.
Is there any option to rotate a shape or tile from their center ?
Thanks to #bg2b for the solution.
Looking to the apple doc linked on #bg2b answer and applied give the following code.
// 1
let redSprite1 = SKShapeNode(rectOf: CGSize(width: 10.0, height: 40.0))
redSprite1.fillColor = .green
redSprite1.position = CGPoint(x: location.x, y: location.y)
redSprite1.zRotation = CGFloat(-Pi/4)
redSprite1.name = "cruz1"
addChild(redSprite1)
// 2
let redSprite2 = SKShapeNode(rectOf: CGSize(width: 10.0, height: 40.0))
redSprite2.fillColor = .red
redSprite2.position = CGPoint(x: location.x, y: location.y)
redSprite2.zRotation = CGFloat(Pi/4)
redSprite2.name = "cruz2"
addChild(redSprite2)

Mask blur with image

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 a line created with UIBezierPath?

I want to move a line. I use touchesMoved for it. But my line is shifted relative to the location of the finger with about 100 pixels. Why does it happen?
func DrawLine()
{
let line = CAShapeLayer()
let linePath = UIBezierPath()
linePath.move(to: to: CGPoint(x: 100, y: 100)
linePath.addLine(to: CGPoint(x: self.view.frame.width - 100, y: 100))
line.path = linePath.cgPath
line.strokeColor = UIColor.red.cgColor
line.lineWidth = 1
line.lineJoin = kCALineJoinRound
self.view.layer.addSublayer(line)
}
override func viewDidLoad()
{
super.viewDidLoad()
DrawLine()
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?)
{
let touch = touches.first
guard let location = touch?.location(in: self.view) else
{
return
}
line.frame.origin = CGPoint(x: location.x-line.frame.size.width/2, y: location.y-line.frame.size.height/2)
}
This is because you are not starting the line you are drawing from CGPoint.zero. Change
linePath.move(to: to: CGPoint(x: 100, y: 100)
linePath.addLine(to: CGPoint(x: self.view.frame.width - 100, y: 100))
to
linePath.move(to: .zero)
linePath.addLine(to: CGPoint(x: 100, y: 0))

Drawn Image Does Not Appear on Camera Overlay Swift

I'm trying to create a drawing app that sits on top of a UIImagePickerController after the picture is taken. The touch delegate functions are called correctly but do not leave any traces on the view as they are supposed to. What I have so far:
func createImageOptionsView() { //creates the view above the picker controller overlay view
let imgOptsView = UIView()
let scale = CGAffineTransform(scaleX: 4.0 / 3.0, y: 4.0 / 3.0)
imgOptsView.tag = 1
imgOptsView.frame = view.frame
let imageView = UIImageView()
imageView.frame = imgOptsView.frame
imageView.translatesAutoresizingMaskIntoConstraints = true
imageView.transform = scale //to account for the removal of the black bar in the camera
let useButton = UIButton()
imageView.tag = 2
imageView.contentMode = .scaleAspectFit
imageView.image = currentImage
useButton.setImage(#imageLiteral(resourceName: "check circle"), for: .normal)
useButton.translatesAutoresizingMaskIntoConstraints = true
useButton.frame = CGRect(x: view.frame.width / 2, y: view.frame.height / 2, width: 100, height: 100)
let cancelButton = UIButton()
colorSlider.previewEnabled = true
colorSlider.translatesAutoresizingMaskIntoConstraints = true
imgOptsView.addSubview(imageView)
imgOptsView.addSubview(useButton)
imgOptsView.addSubview(colorSlider)
imgOptsView.isUserInteractionEnabled = true
useButton.addTarget(self, action: #selector(ViewController.usePicture(sender:)), for: .touchUpInside)
picker.cameraOverlayView!.addSubview(imgOptsView)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
mouseSwiped = false //to check if the touch moved or was simply dotted
let touch: UITouch? = touches.first
lastPoint = touch?.location(in: view) //where to start image context from
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
mouseSwiped = true
let touch: UITouch? = touches.first
let currentPoint: CGPoint? = touch?.location(in: view)
UIGraphicsBeginImageContext(view.frame.size) //begin drawing
tempDrawImage.image?.draw(in: CGRect(x: CGFloat(0), y: CGFloat(0), width: CGFloat(view.frame.size.width), height: CGFloat(view.frame.size.height)))
UIGraphicsGetCurrentContext()?.move(to: CGPoint(x: CGFloat(lastPoint.x), y: CGFloat(lastPoint.y)))
UIGraphicsGetCurrentContext()?.addLine(to: CGPoint(x: CGFloat((currentPoint?.x)!), y: CGFloat((currentPoint?.y)!)))
UIGraphicsGetCurrentContext()!.setLineCap(.round)
UIGraphicsGetCurrentContext()?.setLineWidth(1.0)
UIGraphicsGetCurrentContext()?.setStrokeColor(c)
UIGraphicsGetCurrentContext()!.setBlendMode(.normal)
UIGraphicsGetCurrentContext()?.strokePath() //move from lastPoint to currentPoint with these options
tempDrawImage.image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
lastPoint = currentPoint
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if !mouseSwiped {
UIGraphicsBeginImageContext(view.frame.size)
tempDrawImage.image?.draw(in: CGRect(x: CGFloat(0), y: CGFloat(0), width: CGFloat(view.frame.size.width), height: CGFloat(view.frame.size.height)))
UIGraphicsGetCurrentContext()!.setLineCap(.round)
UIGraphicsGetCurrentContext()?.setLineWidth(1.0)
UIGraphicsGetCurrentContext()?.setStrokeColor(c)
UIGraphicsGetCurrentContext()?.move(to: CGPoint(x: CGFloat(lastPoint.x), y: CGFloat(lastPoint.y)))
UIGraphicsGetCurrentContext()?.addLine(to: CGPoint(x: CGFloat(lastPoint.x), y: CGFloat(lastPoint.y)))
UIGraphicsGetCurrentContext()?.strokePath()
UIGraphicsGetCurrentContext()!.flush()
tempDrawImage.image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
UIGraphicsBeginImageContext(mainImage.frame.size)
mainImage.image?.draw(in: CGRect(x: CGFloat(0), y: CGFloat(0), width: CGFloat(view.frame.size.width), height: CGFloat(view.frame.size.height)), blendMode: .normal, alpha: 1.0)
tempDrawImage.setNeedsDisplay()
mainImage.image = UIGraphicsGetImageFromCurrentImageContext() //paste tempImage onto the main image and then start over
tempDrawImage.image = nil
UIGraphicsEndImageContext()
}
func changedColor(_ slider: ColorSlider) {
c = slider.color.cgColor
}
This is just a guess, but worth a shot. I had a similar problem but in a different context... I was working with AVPlayer when it happened.
In my case, it's because the zPosition wasn't set correctly, so the overlays I was trying to draw appeared behind the video. Assuming that's the case, the fix would be to set the zPosition = -1 which moves it further back like so:
AVPlayerView.layer?.zPosition = -1
This moved the base layer behind the overlay layer (negative numbers move further away from the user in 3D space.)
I would see if the view controlled by your UIImagePickerController allows you to set it's zPosition similar to what AVPlayer allows. If I recall correctly, any view should be able to set it's zPosition. Alternatively, you could try moving your overlay layer to a positive value like zPosition = 1. (I'm not certain, but I would imagine that the default is position is 0.)

Create a subView using CGPath and than adding UIPanGestureRecognizer on that subview

I have seen several answers about adding a GestureRecognzier to subViews but my problem is that I don't have the subView's frame available beforehand. I am drawing a CGPath and at the Touches Ended method, I want to create a new subView with frame equal to CGPath bounding box. After that, I want to drag that subView with PanGestureRecognizer. I am trying to implement Evernote crop functionality where the user selects a certain area of view and move it to other position. Is this the right approach to that solution?
Not quite understand the relationship between UIGestureRecognizer and .frame. you can simply add UIGestureRecognizer to object, once its init work finished.
Try to add gesture in TouchEnd method directly after drawing subview.
import UIKit
class GestureResearchVC: UIViewController{
var subViewByCGPath: UIView?
override func viewDidLoad() {
super.viewDidLoad()
}
func createSubView(){
//creat subview
subViewByCGPath = UIView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
subViewByCGPath?.backgroundColor = UIColor.yellow
let circlePath = UIBezierPath(arcCenter: CGPoint(x: 50,y: 50), radius: CGFloat(20), startAngle: CGFloat(0), endAngle:CGFloat(M_PI * 2), clockwise: true)
let shapeLayer = CAShapeLayer()
shapeLayer.path = circlePath.cgPath
shapeLayer.strokeColor = UIColor.red.cgColor
subViewByCGPath?.layer.addSublayer(shapeLayer)
self.view.addSubview(subViewByCGPath!)
//add pan gesture to subViewByCGPath
let gesture = UIPanGestureRecognizer(target: self, action: #selector(panGestureAction(rec:)))
subViewByCGPath?.addGestureRecognizer(gesture)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if subViewByCGPath == nil {
print("touch end once")
createSubView()
}else{
print("touch end repeatedly")
}
}
func panGestureAction(rec: UIPanGestureRecognizer){
print("pannnnnnn~")
let transPoint = rec.translation(in: self.view)
let x = rec.view!.center.x + transPoint.x
let y = rec.view!.center.y + transPoint.y
rec.view!.center = CGPoint(x: x, y: y)
rec.setTranslation(CGPoint(x: 0, y: 0), in: self.view)
//distinguish state
switch rec.state {
case .began:
print("began")
case .changed:
print("changed")
case .ended:
print("ended")
default:
print("???")
}
}
}

Resources