Swift 3/SpriteKit - Draw independent shapes with finger - ios

I'm a beginner following the tutorial here for drawing shapes in Swift.
import SpriteKit
class GameScene: SKScene {
var activeSlice: SKShapeNode!
var activeSlicePoints = [CGPoint]()
override func didMove(to view: SKView) {
createSlices()
}
func createSlices() {
activeSlice = SKShapeNode()
activeSlice.strokeColor = UIColor(red: 1, green: 0.9, blue: 0, alpha: 1)
activeSlice.lineWidth = 9
addChild(activeSlice)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
if let touch = touches.first {
let location = touch.location(in: self)
activeSlicePoints.append(location)
redrawActiveSlice()
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let location = touch.location(in: self)
activeSlicePoints.append(location)
redrawActiveSlice()
}
override func touchesEnded(_ touches: Set<UITouch>?, with event: UIEvent?) {
}
func redrawActiveSlice() {
let path = UIBezierPath()
path.move(to: activeSlicePoints[0])
for i in 1 ..< activeSlicePoints.count {
path.addLine(to: activeSlicePoints[i])
}
activeSlice.path = path.cgPath
}
}
With this code, when you release the finger then touch again to draw, you have a line connecting the two shapes.
I would like to draw independent shapes.
Perhaps by modifying the code so that for each instance of touchesEnded(), the array of points that represents the shape is stored in a multidimensional array, and by creating an array of points for each new instance of touchesBegan()?
Thank you for your help.

It's something like
override func touchesEnded(_ touches: Set<UITouch>?, with event: UIEvent?) {
createSlices()
}

Related

How to change the clickable area of a SpriteKit Sprite Node from rectangle to circle?

I have a play button on my menu screen but it detects touches in the area of the entire rectangle instead of just the circle. Is there any way to change the clickable bounds to a circle either within xcode?
Or will i have to programmatically do it within initialization or a function:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {}
Just add it programmatically to the GameScene.
var playButton: SKSpriteNode!
override func didMove(to view: SKView) {
playButton = SKSpriteNode(imageNamed: "playButton")
playButton.name = "playButton"
playButton.position = CGPoint(x: self.frame.midX , y: self.frame.midY)
playButton.zPosition = 0
playButton.alpha = 1
addChild(playButton)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else {
return
}
let pos = touch.location(in: self)
let node = self.atPoint(pos)
if node.name == "playButton"{
//Do Something
}
}

Drawing lines with core graphics

I want to draw more than one straight line in core graphics. or save the line and start a new one. I used two variables for touch location and assigned touch location to them in touch Began,Moved,Ended then I used this:
override func draw(_ rect: CGRect) {
let context = UIGraphicsGetCurrentContext()
context?.setStrokeColor(UIColor(red: 0, green: 0, blue: 0, alpha: 1.0).cgColor)
context?.setLineWidth(5.0)
context?.move(to: CGPoint(x: firstTouchLocation.x, y: firstTouchLocation.y))
context?.addLine(to: CGPoint(x: lastTouchLocation.x, y: lastTouchLocation.y))
context?.strokePath()
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
firstTouchLocation = touch.location(in: self)
lastTouchLocation = firstTouchLocation
setNeedsDisplay()
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
lastTouchLocation = touch.location(in: self)
setNeedsDisplay()
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
lastTouchLocation = touch.location(in: self)
setNeedsDisplay()
}
}
Try drawing the line using CAShapeLayer like so:
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.red.cgColor
line.lineWidth = 1
line.lineJoin = kCALineJoinRound
self.view.layer.addSublayer(line)
}
Hope this helps!
You got to have a model object which represent a line say ex: LineAnnotation. LineAnnotation will hold start and end points, color and many others details about the line. Hold every LineAnnotation(line) in an array. Iterate through this array and draw line one by one. In your case are drawing using latest data. Previous points which are drawn will e refreshed when you call setNeedsDisplay()
As per apples documentation(https://developer.apple.com/documentation/uikit/uiview/1622437-setneedsdisplay) setNeedsDisplay method redraws your view, so you are left with fresh view with latest line only.
To solve your problem simply pull your code in a method and call that method whenever you made a touch.
override func draw(_ rect: CGRect) {
drawLine()
}
func drawLine()
{
let context = UIGraphicsGetCurrentContext()
context?.setStrokeColor(UIColor(red: 0, green: 0, blue: 0, alpha: 1.0).cgColor)
context?.setLineWidth(5.0)
context?.move(to: CGPoint(x: firstTouchLocation.x, y: firstTouchLocation.y))
context?.addLine(to: CGPoint(x: lastTouchLocation.x, y: lastTouchLocation.y))
context?.strokePath()
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
firstTouchLocation = touch.location(in: self)
lastTouchLocation = firstTouchLocation
drawLine()
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
lastTouchLocation = touch.location(in: self)
drawLine()
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
lastTouchLocation = touch.location(in: self)
drawLine()
}
}

Moving a Sprite along a UIBezierPath

I have been having some trouble moving a Sprite along a UIBezierPath. I am getting this path from my view controller and it is passing along the correct path. Any ideas on why this isn't animating? Eventually I want to be able to move multiple sprites along different bezier paths.
func play(){
let img = SKSpriteNode(imageNamed:"ball.png")
img.position = CGPoint(x: self.frame.size.width/4, y: self.frame.size.height/4)
self.addChild(img)
new_path = viewController.path //UIBezierPath is being returned
let followPath = SKAction.follow(new_path.cgPath, asOffset: true, orientToPath: false, duration: 5.0)
img.run(followPath)
}
I am trying to draw a bezier path here(Kind of like a whiteboard app)in a custom view and then access the bezier path when I want to animate it. The path seems to be passed through correctly.
private var path: UIBezierPath!
let newpath = UIBezierPath()
func initBezierPath() {
path = UIBezierPath()
path.lineCapStyle = CGLineCap.round
path.lineJoinStyle = CGLineJoin.round
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch: AnyObject? = touches.first
lastPoint = touch!.location(in: self)
pointCounter = 0
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch: AnyObject? = touches.first
let newPoint = touch!.location(in: self)
path.move(to: lastPoint)
path.addLine(to: newPoint)
lastPoint = newPoint
pointCounter += 1
if pointCounter == pointLimit {
pointCounter = 0
renderToImage()
setNeedsDisplay()
newpath.append(path)
path.removeAllPoints()
}
else {
setNeedsDisplay()
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
pointCounter = 0
renderToImage()
setNeedsDisplay()
newpath.append(path)
path.removeAllPoints()
}

touch moking sprite by tap not swipe swift

I have a block in my code where the sprite will only move if you tap the screen. I would like to have this behaviour when I swipe up or down instead. Here is my code
import SpriteKit
import UIKit
class GameScene: SKScene {
var porker:Porker!
var touchLocation = CGFloat()
var gameOver = false
override func didMoveToView(view: SKView) {
addBG()
addPig()
}
func addBG() {
let bg = SKSpriteNode(imageNamed: "bg");
addChild(bg)
}
func addPig() {
let Pig = SKSpriteNode(imageNamed: "pig")
porker = Porker(guy:Pig)
addChild(Pig)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch: AnyObject in touches{
if !gameOver {
touchLocation = (touch.locationInView(self.view!).y * -1) + (self.size.height/2)
}
}
let moveAction = SKAction.moveToY(touchLocation, duration: 0.5)
moveAction.timingMode = SKActionTimingMode.EaseOut
porker.guy.runAction(moveAction)
}
}
func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
Replace touchesBegan with this code:
// Store the start touch position
let yTouchCurrentPosition = 0.0
let yTouchDistance = 0.0
let yTouchStartPosition = 0.0
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
yTouchStartPosition = touch.locationInNode(self).y
}
}
// Calculate the distance of the touch movement
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
yTouchCurrentPosition = touch.locationInNode(self).y
yTouchDistance = yTouchStartPosition - yTouchCurrentPosition
}
}
// Reset all movement states and move sprite
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
yTouchCurrentPosition = 0.0
yTouchDistance = 0.0
yTouchStartPosition = 0.0
let moveAction = SKAction.moveToY(CGPoint(porker.guy.location.x, porker.guy.location.y + yTouchDistance), duration: 0.5)
moveAction.timingMode = SKActionTimingMode.EaseOut
porker.guy.runAction(moveAction)
}

Removing lagging latency in drawing UIBezierPath smooth lines in Swift

The code below draws smooth curved lines by overriding touches, but there is noticeable lagging or latency. The code uses addCurveToPoint and calls setNeedsDisplay after every 4 touch points which causes a jumpy appearance as the drawing doesn't keep up with finger movements. To remove the lagging or perceived latency, touch points 1, 2, 3 (leading up to touch point 4) could be temporarily filled with addQuadCurveToPoint and addLineToPoint.
How can this actually be achieved in code to remove perceived lagging by using a temporary Line and QuadCurved line before displaying a final Curved line?
If the below class is attached to one UIView (e.g. viewOne or self), how do I make a copy of the drawing to another UIView outside the class (e.g. viewTwo) after touchesEnded?
// ViewController.swift
import UIKit
class drawSmoothCurvedLinesWithLagging: UIView {
let path=UIBezierPath()
var incrementalImage:UIImage?
var points = [CGPoint?](count: 5, repeatedValue: nil)
var counter:Int?
var strokeColor:UIColor?
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func drawRect(rect: CGRect) {
autoreleasepool {
incrementalImage?.drawInRect(rect)
strokeColor = UIColor.blueColor()
strokeColor?.setStroke()
path.lineWidth = 20
path.lineCapStyle = CGLineCap.Round
path.stroke()
}
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
counter = 0
let touch: AnyObject? = touches.first
points[0] = touch!.locationInView(self)
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch: AnyObject? = touches.first
let point = touch!.locationInView(self)
counter = counter! + 1
points[counter!] = point
if counter == 2{
//use path.addLineToPoint ?
//use self.setNeedsDisplay() ?
}
if counter == 3{
//use path.addQuadCurveToPoint ?
//use self.setNeedsDisplay() ?
}
if counter == 4{
points[3]! = CGPointMake((points[2]!.x + points[4]!.x)/2.0, (points[2]!.y + points[4]!.y)/2.0)
path.moveToPoint(points[0]!)
path.addCurveToPoint(points[3]!, controlPoint1: points[1]!, controlPoint2: points[2]!)
self.setNeedsDisplay()
points[0]! = points[3]!
points[1]! = points[4]!
counter = 1
}
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
self.drawBitmap()
self.setNeedsDisplay()
path.removeAllPoints()
counter = 0
}
override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
self.touchesEnded(touches!, withEvent: event)
}
func drawBitmap(){
UIGraphicsBeginImageContextWithOptions(self.bounds.size, true, 0.0)
strokeColor?.setStroke()
if((incrementalImage) == nil){
let rectPath:UIBezierPath = UIBezierPath(rect: self.bounds)
UIColor.whiteColor().setFill()
rectPath.fill()
}
incrementalImage?.drawAtPoint(CGPointZero)
path.stroke()
incrementalImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Yes, adding a curve every few points will give it a stuttering lag. So, yes, you can reduce this affect by adding a line to points[1], adding a quad curve to points[2] and adding a cubic curve to points[3].
As you said, make sure to add this to a separate path, though. So, in Swift 3/4:
class SmoothCurvedLinesView: UIView {
var strokeColor = UIColor.blue
var lineWidth: CGFloat = 20
var snapshotImage: UIImage?
private var path: UIBezierPath?
private var temporaryPath: UIBezierPath?
private var points = [CGPoint]()
override func draw(_ rect: CGRect) {
snapshotImage?.draw(in: rect)
strokeColor.setStroke()
path?.stroke()
temporaryPath?.stroke()
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
points = [touch.location(in: self)]
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let point = touch.location(in: self)
points.append(point)
updatePaths()
setNeedsDisplay()
}
private func updatePaths() {
// update main path
while points.count > 4 {
points[3] = CGPoint(x: (points[2].x + points[4].x)/2.0, y: (points[2].y + points[4].y)/2.0)
if path == nil {
path = createPathStarting(at: points[0])
}
path?.addCurve(to: points[3], controlPoint1: points[1], controlPoint2: points[2])
points.removeFirst(3)
temporaryPath = nil
}
// build temporary path up to last touch point
if points.count == 2 {
temporaryPath = createPathStarting(at: points[0])
temporaryPath?.addLine(to: points[1])
} else if points.count == 3 {
temporaryPath = createPathStarting(at: points[0])
temporaryPath?.addQuadCurve(to: points[2], controlPoint: points[1])
} else if points.count == 4 {
temporaryPath = createPathStarting(at: points[0])
temporaryPath?.addCurve(to: points[3], controlPoint1: points[1], controlPoint2: points[2])
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
finishPath()
}
override func touchesCancelled(_ touches: Set<UITouch>?, with event: UIEvent?) {
finishPath()
}
private func finishPath() {
constructIncrementalImage()
path = nil
setNeedsDisplay()
}
private func createPathStarting(at point: CGPoint) -> UIBezierPath {
let localPath = UIBezierPath()
localPath.move(to: point)
localPath.lineWidth = lineWidth
localPath.lineCapStyle = .round
localPath.lineJoinStyle = .round
return localPath
}
private func constructIncrementalImage() {
UIGraphicsBeginImageContextWithOptions(bounds.size, false, 0.0)
strokeColor.setStroke()
snapshotImage?.draw(at: .zero)
path?.stroke()
temporaryPath?.stroke()
snapshotImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
}
You could even marry this with iOS 9 predictive touches (as I described in my other answer), which could reduce lag even further.
To take this resulting image and use it elsewhere, you can just grab the incrementalImage (which I renamed to snapshotImage, above), and drop it into an image view of the other view.
For Swift 2 rendition, see previous revision of this answer.

Resources