Nodes in SKShapeNode reappearing when creating another new node - ios

Im trying to write the code for a line that draws with the drag of a finger but deletes when the finger is removed (in SpriteKit and Swift 3)
var shapeNodes = [SKShapeNode]()
var pathToDraw = CGMutablePath()
var lineNode = SKShapeNode()
func deleteAllShapeNodes() {
for node in shapeNodes {
node.removeFromParent()
}
shapeNodes.removeAll(keepingCapacity: false)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches {
firstPoint = touch.location(in: self)
}
shapeNodes.append(lineNode)
pathToDraw.move(to: CGPoint(x: firstPoint.x, y: firstPoint.y))
lineNode.lineWidth = 4
lineNode.strokeColor = UIColor.white
lineNode.name = "Line"
lineNode.zPosition = 100000
lineNode.path = pathToDraw
self.addChild(lineNode)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches{
positionInScene = touch.location(in: self)
}
shapeNodes.append(lineNode)
pathToDraw.addLine(to: CGPoint(x: positionInScene.x, y: positionInScene.y))
lineNode.path = pathToDraw
firstPoint = positionInScene
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches{
TouchEndPosition = touch.location(in: self)
}
self.deleteAllShapeNodes()
}
the first line draws and deletes perfectly but when i start drawing the second line the first line reappears.

Creating a brand-new CGMutablePath seems to be the only way to get an empty CGMutablePath.
And you are adding the only instance of SKShapeNode (kept in lineNode) into shapeNodes, it's non-sense. Your lineNode keeps all the lines you added to your pathToDraw.
Generally, you may need to learn when to instantiate objects, and when to release them.
Try this:
//### You are adding the same instance multiple times into this array, it's nonsense.
//var shapeNodes = [SKShapeNode]()
//### `pathToDraw` can be kept in the `SKShapeNode`.
//var pathToDraw = CGMutablePath()
//### In this case, this is not a good place to instantiate an `SKShapeNode`.
var lineNode: SKShapeNode?
func deleteAllShapeNodes() {
//### Release the `SKShapeNode` contained in `lineNode` to delete all lines.
lineNode?.removeFromParent()
lineNode = nil
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let firstPoint = touches.first!.location(in: self)
self.lineNode?.removeFromParent()
//### If you want somethings which live while dragging, `touchesBegan(_:with:)` is a good place to instantiate them.
let lineNode = SKShapeNode()
//### Create an empty new `CGMutablePath` at each `touchesBegan(_:with:)`.
let pathToDraw = CGMutablePath()
self.lineNode = lineNode
pathToDraw.move(to: firstPoint)
lineNode.lineWidth = 4
lineNode.strokeColor = UIColor.white
lineNode.name = "Line"
lineNode.zPosition = 100000
//### `pathToDraw` is kept in the `SKShapeNode`.
lineNode.path = pathToDraw
self.addChild(lineNode)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let positionInScene = touches.first!.location(in: self)
if let lineNode = self.lineNode {
//### Modify the `CGMutablePath` kept in the `SKShapeNode`.
let pathToDraw = lineNode.path as! CGMutablePath
pathToDraw.addLine(to: positionInScene)
//### Refresh the shape of the `SKShapeNode`.
lineNode.path = pathToDraw
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
self.deleteAllShapeNodes()
}

Related

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()
}

Swift 3/SpriteKit - Draw independent shapes with finger

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()
}

Spritekit | SWIFT 3 | - Cannot convert value of type Void (aka'()' to expected argument type 'unsafeRawPointer'

Cannot convert value of type Void (aka'()' to expected argument type 'unsafeRawPointer'
The code allows me to draw lines on the screen by touch. I am using a dictionary to allow multiple the user to draw multiple lines simultaneously.
Its just when I am trying to store the pointers in a NSValue key.
This was working fine in Objective C before I started upgrading to Swift 3.
var lines: NSMutableDictionary!
override func didMove(to view: SKView) {
lines = NSMutableDictionary.init(capacity: 4)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches {
let location: CGPoint = t.location(in: self)
// Create a mutable path
let path: UIBezierPath = UIBezierPath()
path.move(to: location)
// Create a shape node using the path
lineNode = SKShapeNode(path: path.cgPath)
lineNode.name = "sprite\(i)"
self.addChild(lineNode)
// Use touch pointer as the dictionary key. Since the dictionary key must conform to
// NSCopying, box the touch pointer in an NSValue
var key = NSValue(pointer: (t as! Void)) -- **ERROR HERE**
lines[key] = lineNode
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches {
let location = t.location(in: self)
// Retrieve the shape node that corresponds to touch
let key = NSValue(pointer: (touches as! Void)) -- **ERROR HERE**
lineNode = (lines[key] as! String)
if lineNode != nil {
// Create and initial a mutable path with the lineNode's path
let path = UIBezierPath(cgPath: lineNode.path!)
// Add a line to the current touch point
path.addLine(to: location)
// Update lineNode
lineNode.path = path.cgPath
lineNode.physicsBody = SKPhysicsBody(edgeChainFrom: path.cgPath)
lineNode.name = lineNodeCategoryName
}
}
}
Does anybody know why I am getting this and how I can resolve the error ?
Maybe you are confusing (void *)... casting in Objective-C and as! Void in Swift. They are completely different.
As UITouch is Hashable, you can use it as a Key of Swift Dictionary.
Try using it:
var lines: [UITouch: SKShapeNode] = [:]
override func didMove(to view: SKView) {
lines = Dictionary(minimumCapacity: 4)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches {
let location: CGPoint = t.location(in: self)
// Create a mutable path
let path: UIBezierPath = UIBezierPath()
path.move(to: location)
// Create a shape node using the path
let lineNode = SKShapeNode(path: path.cgPath)
lineNode.name = "sprite\(i)"
self.addChild(lineNode)
lines[t] = lineNode
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches {
let location = t.location(in: self)
// Retrieve the shape node that corresponds to touch
if let lineNode = lines[t] {
// Create and initial a mutable path with the lineNode's path
let path = UIBezierPath(cgPath: lineNode.path!)
// Add a line to the current touch point
path.addLine(to: location)
// Update lineNode
lineNode.path = path.cgPath
lineNode.physicsBody = SKPhysicsBody(edgeChainFrom: path.cgPath)
lineNode.name = lineNodeCategoryName
}
}
}
I didn't know this usage of UITouch, but as you say your Objective-C version using NSValue actually works, my code above should work.

Throwing an object with SpriteKit

I have the following code at the moment. Even though the code build is successful, i cannot seem to get it to work. I am trying to make it so when you flick the object, it moves at the velocity of your begin and end touch.
import SpriteKit
class GameScene: SKScene {
var sprite: SKSpriteNode!
var touchPoint: CGPoint = CGPoint()
var touching: Bool = false
override func didMoveToView(view: SKView) {
self.physicsBody = SKPhysicsBody(edgeLoopFromRect: self.frame)
sprite = SKSpriteNode(color: UIColor.redColor(), size: CGSize(width: 50, height: 50))
sprite.physicsBody = SKPhysicsBody(rectangleOfSize: sprite.size)
sprite.position = CGPoint(x: self.size.width/2.0, y: self.size.height/2.0)
self.addChild(sprite)
}
//for touch: AnyObject in touches {
//let location = touch.locationInNode(self)
//let touchedNode = self.nodeAtPoint(location)
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if sprite.frame.contains(location) {
touchPoint = location
touching = true
}
}
func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
touchPoint = location
}
func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
touching = false
}
func update(currentTime: CFTimeInterval) {
if touching {
let dt:CGFloat = 1.0/60.0
let distance = CGVector(dx: touchPoint.x-sprite.position.x, dy: touchPoint.y-sprite.position.y)
let velocity = CGVector(dx: distance.dx/dt, dy: distance.dy/dt)
sprite.physicsBody!.velocity = velocity
}
}
}}}
You accidentally placed touchesMoved, touchesEnded and update inside touchesBegan. Besides that your code works. A hint that there were problems was the fact you didn't need to prefix touchesMoved, touchesEnded or update with override.
In the future, I would recommend using breakpoints and print statements to check the methods you expect to execute, are in fact running. Doing that you'd see that your versions of touchesMoved, touchesEnded and update weren't being called.
Anyway, here's it corrected it and now it works perfectly:
import SpriteKit
class GameScene: SKScene {
var sprite: SKSpriteNode!
var touchPoint: CGPoint = CGPoint()
var touching: Bool = false
override func didMoveToView(view: SKView) {
self.physicsBody = SKPhysicsBody(edgeLoopFromRect: self.frame)
sprite = SKSpriteNode(color: UIColor.redColor(), size: CGSize(width: 50, height: 50))
sprite.physicsBody = SKPhysicsBody(rectangleOfSize: sprite.size)
sprite.position = CGPoint(x: self.size.width/2.0, y: self.size.height/2.0)
self.addChild(sprite)
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if sprite.frame.contains(location) {
touchPoint = location
touching = true
}
}
}
override func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
touchPoint = location
}
}
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
touching = false
}
override func touchesCancelled(touches: Set<NSObject>!, withEvent event: UIEvent!) {
touching = false
}
override func update(currentTime: NSTimeInterval) {
if touching {
let dt:CGFloat = 1.0/60.0
let distance = CGVector(dx: touchPoint.x-sprite.position.x, dy: touchPoint.y-sprite.position.y)
let velocity = CGVector(dx: distance.dx/dt, dy: distance.dy/dt)
sprite.physicsBody!.velocity = velocity
}
}
}
ABakerSmith's solutions updated for Swift 4:
import SpriteKit
class GameScene: SKScene {
var sprite: SKSpriteNode!
var touchPoint: CGPoint = CGPoint()
var touching: Bool = false
override func didMove(to view: SKView) {
self.physicsBody = SKPhysicsBody.init(edgeLoopFrom: self.frame)
sprite = SKSpriteNode(color: .red, size: CGSize(width: 50, height: 50))
sprite.physicsBody = SKPhysicsBody.init(rectangleOf: sprite.size)
sprite.position = CGPoint(x: self.size.width/2.0, y: self.size.height/2.0)
self.addChild(sprite)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first?.location(in: self) {
if sprite.frame.contains(touch) {
touchPoint = touch
touching = true
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches {
let location = t.location(in: self)
touchPoint = location
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
touching = false
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
touching = false
}
override func update(_ currentTime: TimeInterval) {
if touching {
let dt:CGFloat = 1.0/60.0
let distance = CGVector(dx: touchPoint.x-sprite.position.x, dy: touchPoint.y-sprite.position.y)
let velocity = CGVector(dx: distance.dx/dt, dy: distance.dy/dt)
sprite.physicsBody!.velocity = velocity
}
}
}
Solution updated to not have the touch/drag snap to the middle of the sprite. If you're dragging/throwing an object you're going to want that throw to come from where the user touched the object and not snap to the middle of the object. This will make it look a lot smoother
import SpriteKit
class GameScene: SKScene {
var sprite: SKSpriteNode!
var touchPoint: CGPoint = CGPoint()
var touching: Bool = false
var xDif = CGFloat()
var yDif = CGFloat()
override func didMove(to view: SKView) {
self.physicsBody = SKPhysicsBody.init(edgeLoopFrom: self.frame)
sprite = SKSpriteNode(color: .red, size: CGSize(width: 50, height: 50))
sprite.physicsBody = SKPhysicsBody.init(rectangleOf: sprite.size)
sprite.position = CGPoint(x: self.size.width/2.0, y: self.size.height/2.0)
self.addChild(sprite)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first?.location(in: self) {
xDif = sprite.position.x - touch.x
yDif = sprite.position.y - touch.y
if sprite.frame.contains(touch) {
touchPoint = touch
touching = true
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches {
let location = t.location(in: self)
touchPoint = location
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
touching = false
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
touching = false
}
override func update(_ currentTime: TimeInterval) {
if touching {
let dt:CGFloat = 1.0/60.0
let distance = CGVector(dx: touchPoint.x-sprite.position.x + xDif, dy: touchPoint.y-sprite.position.y + yDif)
let velocity = CGVector(dx: distance.dx/dt, dy: distance.dy/dt)
sprite.physicsBody!.velocity = velocity
}
}
}

Resources