How can I detect swipes on Spritekit Nodes? - ios

So I am currently working on a 2D endless runner written in Swift3 and using Spritekit for my nodes and scenes. I recently implemented some code to detect swipes in general, they will be below. So my question is: how can I detect a swipe action and check the direction of that swipe on a Spritekit Node? I realize that this has been asked before, but I cannot find a working solution since everything I come across seems to be for Swift and Swift2, not Swift3.
Here's my swipe detection code:
override func viewDidLoad() {
super.viewDidLoad()
let swipeLeft = UISwipeGestureRecognizer(target: self, action: #selector(self.respondToSwipeGesture))
swipeLeft.direction = UISwipeGestureRecognizerDirection.left
self.view.addGestureRecognizer(swipeLeft)
let swipeRight = UISwipeGestureRecognizer(target: self, action: #selector(self.respondToSwipeGesture))
swipeRight.direction = UISwipeGestureRecognizerDirection.right
self.view.addGestureRecognizer(swipeRight)
let swipeUp = UISwipeGestureRecognizer(target: self, action: #selector(self.respondToSwipeGesture))
swipeUp.direction = UISwipeGestureRecognizerDirection.up
self.view.addGestureRecognizer(swipeUp)
let swipeDown = UISwipeGestureRecognizer(target: self, action: #selector(self.respondToSwipeGesture))
swipeDown.direction = UISwipeGestureRecognizerDirection.down
self.view.addGestureRecognizer(swipeDown)
}
I have tested these with a function:
func respondToSwipeGesture(gesture: UIGestureRecognizer) {
if let swipeGesture = gesture as? UISwipeGestureRecognizer {
switch swipeGesture.direction {
case UISwipeGestureRecognizerDirection.right:
print("Swiped right")
case UISwipeGestureRecognizerDirection.down:
print("Swiped down")
case UISwipeGestureRecognizerDirection.left:
print("Swiped left")
case UISwipeGestureRecognizerDirection.up:
print("Swiped up")
default:
break
}
}
}
The swipes seem to work all right, as the print statements have been displaying the correct outputs in response to my swipes.
I have also added a delegate to avoid possible SIGABRT errors:
class GameViewController: UIViewController, UIGestureRecognizerDelegate
Please let me know if you'd like more information!
Edit:
Added code where I initialized my puzzle pieces
//The class
class ChoosePiecesClass: SKSpriteNode{
func moveWithCamera() {
self.position.x += 5;
}
//initialize a piece
private var RandomPiece1: ChoosePiecesClass?;
override func didMove(to view: SKView) {
RandomPiece1 = childNode(withName: "RandomPiece1") as? ChoosePiecesClass;
RandomPiece1?.position.x = 195;
RandomPiece1?.position.y = -251;
}
override func update(_ currentTime: TimeInterval) {
RandomPiece1?.moveWithCamera();
}
Edited to provide the error statement from console, upon clicking Start Game and triggering the fatalError code, causing the game to crash.
fatal error: init(coder:) has not been implemented: file /Users/ardemis/Documents/PuzzleRunner/PuzzleRunner 3 V3/PuzzleRunner/ChoosePieces.swift, line 33
It also displays the following
EXEC_BAT_INSTRUCTION(code = EXEC_1386_INVOP, subcode = 0x0)
on the same line as the fatalError declaration.

I wouldn't use the UIGestures if I was trying to track specific SpriteNodes. You can easily track your nodes in TouchesBegan and figure out which way a swipe direction occurs. This example has three sprites on the screen on will print/log whatever direction one of them gets swiped and will ignore all other swipes.
EDIT > I just created a subclass for my Box object (sorry I didn't use your naming, but it has the same functions). There are probably many ways of doing this, I chose to use create a protocol on the subclass and make the scene conform to the protocol. I moved all of the touch/swipe functionality into the sub class, and when it is done detecting the swipe it just calls the delegate and says which object has been swiped.
protocol BoxDelegate: NSObjectProtocol {
func boxSwiped(box: Box)
}
class Box: SKSpriteNode {
weak var boxDelegate: BoxDelegate!
private var moveAmtX: CGFloat = 0
private var moveAmtY: CGFloat = 0
private let minimum_detect_distance: CGFloat = 100
private var initialPosition: CGPoint = CGPoint.zero
private var initialTouch: CGPoint = CGPoint.zero
private var resettingSlider = false
override init(texture: SKTexture?, color: UIColor, size: CGSize) {
super.init(texture: texture, color: color, size: size)
self.isUserInteractionEnabled = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func moveWithCamera() {
self.position.x += 5
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first as UITouch! {
initialTouch = touch.location(in: self.scene!.view)
moveAmtY = 0
moveAmtX = 0
initialPosition = self.position
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first as UITouch! {
let movingPoint: CGPoint = touch.location(in: self.scene!.view)
moveAmtX = movingPoint.x - initialTouch.x
moveAmtY = movingPoint.y - initialTouch.y
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
var direction = ""
if fabs(moveAmtX) > minimum_detect_distance {
//must be moving side to side
if moveAmtX < 0 {
direction = "left"
}
else {
direction = "right"
}
}
else if fabs(moveAmtY) > minimum_detect_distance {
//must be moving up and down
if moveAmtY < 0 {
direction = "up"
}
else {
direction = "down"
}
}
print("object \(self.name!) swiped " + direction)
self.boxDelegate.boxSwiped(box: self)
}
}
In GameScene.sks
Make sure to make GameScene conform to BoxDelegate by adding the protocol after the declaration
class GameScene: SKScene, BoxDelegate {
var box = Box()
var box2 = Box()
var box3 = Box()
override func didMove(to view: SKView) {
box = Box(color: .white, size: CGSize(width: 200, height: 200))
box.zPosition = 1
box.position = CGPoint(x: 0 - 900, y: 0)
box.name = "white box"
box.boxDelegate = self
addChild(box)
box2 = Box(color: .blue, size: CGSize(width: 200, height: 200))
box2.zPosition = 1
box2.name = "blue box"
box2.position = CGPoint(x: 0 - 600, y: 0)
box2.boxDelegate = self
addChild(box2)
box3 = Box(color: .red, size: CGSize(width: 200, height: 200))
box3.zPosition = 1
box3.name = "red box"
box3.position = CGPoint(x: -300, y: 0)
box3.boxDelegate = self
addChild(box3)
}
func boxSwiped(box: Box) {
currentObject = box
print("currentObject \(currentObject)")
}
override func update(_ currentTime: TimeInterval) {
box.moveWithCamera()
box2.moveWithCamera()
box3.moveWithCamera()
}
}

Related

SKScene nodes not detecting touch

I am trying to make an ARKit app for ios and the nodes in the scene are not responding to touch. The scene is properly displayed but I haven't been able to detect any touch.
fileNamed: "TestScene" refers to a TestScene.sks file in my project which is empty and I add the node in the code as shown below.
let detailPlane = SCNPlane(width: xOffset, height: xOffset * 1.4)
let testScene = SKScene(fileNamed: "TestScene")
testScene?.isUserInteractionEnabled = true
let winner = TouchableNode(fontNamed: "Chalkduster")
winner.text = "You Win!"
winner.fontSize = 65
winner.fontColor = SKColor.green
winner.position = CGPoint(x: 0, y: 0)
testScene?.addChild(winner)
let material = SCNMaterial()
material.diffuse.contents = testScene
material.diffuse.contentsTransform = SCNMatrix4Translate(SCNMatrix4MakeScale(1, -1, 1), 0, 1, 0)
detailPlane.materials = [material]
let node = SCNNode(geometry: detailPlane)
rootNode.addChildNode(node)
For TouchableNode I have the following class
class TouchableNode : SKLabelNode {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("Touch detected")
}
}
I've achieved this affect using gesture recognize
private func registerGestureRecognizers() -> Void {
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap))
sceneView.addGestureRecognizer(tapGestureRecognizer)
}
then have a function to handle the tap gesture
#objc private func handleTap(sender: UITapGestureRecognizer) -> Void {
let sceneViewTappedOn = sender.view as! SCNView
let touchCoordinates = sender.location(in: sceneViewTappedOn)
let hitTest = sceneViewTappedOn.hitTest(touchCoordinates)
if !hitTest.isEmpty {
let hitResults = hitTest.first!
var hitNode = hitResults.node
// do something with the node that has been tapped
}
}
}
You need to do isUserInteractionEnabled = true first.
So, something like:
class TouchableNode : SKLabelNode {
override init() {
super.init()
isUserInteractionEnabled = true
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
isUserInteractionEnabled = true
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("Touch detected")
}
}

How can I set UIGestureRecognizer's Speed as the velocity for my Physicsbody in SpriteKit?

I calculated the speed of UIGestureRecognizer and I want to use it as velocity of Physicsbody in SpriteKit.
How can I do that since Speed is calculated in touches began and touches ended method, while my Physicsbody is wrapped in swipeUp & swipeDown etc functions, which does not have access to the Speed variable?
Here is my code:
class GameScene: SKScene, SKPhysicsContactDelegate, SKViewDelegate, UIGestureRecognizerDelegate, UIViewControllerTransitioningDelegate {
var Kite = SKSpriteNode(imageNamed: "ASC_8025_large.jpg")
#objc let minusButton = SKSpriteNode(imageNamed: "minus.jpg")
#objc let plusButton = SKSpriteNode(imageNamed: "plus.png")
//override init(Kite: SKSpriteNode) {
//For long Long press gesture to work
let longPressGestureRecPlus = UILongPressGestureRecognizer()
let longPressGestureRecMinus = UILongPressGestureRecognizer(target: self, action: #selector(longPressed(press:)))
//First we declare all of our Gestures...
//swipes
let swipeRightRec = UISwipeGestureRecognizer()
let swipeLeftRec = UISwipeGestureRecognizer()
let swipeUpRec = UISwipeGestureRecognizer()
let swipeDownRec = UISwipeGestureRecognizer()
//rotate
let rotateRec = UIRotationGestureRecognizer()
//taps
let tapRec = UITapGestureRecognizer()
let tapRec2 = UITapGestureRecognizer()
override func didMove(to view: SKView) {
// Get label node from scene and store it for use later
Kite.size = CGSize(width: 40.0, height: 40.0)
Kite.position = CGPoint(x: frame.midX, y: frame.midY)
plusButton.size = CGSize(width: 30.0, height: 30.0)
plusButton.position = CGPoint(x: frame.midX, y: frame.minY + 20.0)
plusButton.name = "plusButton"
//plusButton.isUserInteractionEnabled = true
addChild(Kite)
addChild(plusButton)
swipeRightRec.addTarget(self, action: #selector(GameScene.swipedRight) )
swipeRightRec.direction = .right
self.view!.addGestureRecognizer(swipeRightRec)
swipeLeftRec.addTarget(self, action: #selector(GameScene.swipedLeft) )
swipeLeftRec.direction = .left
self.view!.addGestureRecognizer(swipeLeftRec)
swipeUpRec.addTarget(self, action: #selector(GameScene.swipedUp) )
swipeUpRec.direction = .up
self.view!.addGestureRecognizer(swipeUpRec)
swipeDownRec.addTarget(self, action: #selector(GameScene.swipedDown) )
swipeDownRec.direction = .down
self.view!.addGestureRecognizer(swipeDownRec)
//notice the function this calls has (_:) after it because we are passing in info about the gesture itself (the sender)
rotateRec.addTarget(self, action: #selector (GameScene.rotatedView (_:) ))
self.view!.addGestureRecognizer(rotateRec)
// again notice (_:), we'll need this to find out where the tap occurred.
tapRec.addTarget(self, action:#selector(GameScene.tappedView(_:) ))
tapRec.numberOfTouchesRequired = 1
tapRec.numberOfTapsRequired = 1
self.view!.addGestureRecognizer(tapRec)
tapRec2.addTarget(self, action:#selector(GameScene.tappedView2(_:) ))
tapRec2.numberOfTouchesRequired = 1
tapRec2.numberOfTapsRequired = 2 //2 taps instead of 1 this time
self.view!.addGestureRecognizer(tapRec2)
longPressGestureRecPlus.addTarget(self, action: #selector(longPressed(press:)))
longPressGestureRecPlus.minimumPressDuration = 2.0
self.view?.addGestureRecognizer(longPressGestureRecPlus)
}
//the functions that get called when swiping...
#objc func swipedRight() {
print("Right")
Kite.physicsBody?.velocity = CGVector(dx: 60, dy: 60)
//Tilts the Kite towards Right
let tiltRight = SKAction.rotate(toAngle: -1.00, duration: 0.1)
Kite.run(tiltRight)
}
#objc func swipedLeft() {
Kite.physicsBody?.velocity = CGVector(dx: -60, dy: 60)
//Tilts the Kite towards Right
let tiltLeft = SKAction.rotate(toAngle: 1.00, duration: 0.1)
Kite.run(tiltLeft)
print("Left")
}
#objc func swipedUp() {
Kite.physicsBody?.velocity = CGVector(dx: 60, dy: 60)
//Straightens the Kite
let straightens = SKAction.rotate(toAngle: 0.00, duration: 0.1)
Kite.run(straightens)
print("Up")
}
#objc func swipedDown() {
Kite.physicsBody?.velocity = CGVector(dx: 0, dy: -60)
print("Down")
}
// what gets called when there's a single tap...
//notice the sender is a parameter. This is why we added (_:) that part to the selector earlier
#objc func tappedView(_ sender:UITapGestureRecognizer) {
let point:CGPoint = sender.location(in: self.view)
print("Single tap")
print(point)
}
// what gets called when there's a double tap...
//notice the sender is a parameter. This is why we added (_:) that part to the selector earlier
#objc func tappedView2(_ sender:UITapGestureRecognizer) {
let point:CGPoint = sender.location(in: self.view)
print("Double tap")
print(point)
}
//what gets called when there's a rotation gesture
//notice the sender is a parameter. This is why we added (_:) that part to the selector earlier
#objc func rotatedView(_ sender:UIRotationGestureRecognizer) {
if (sender.state == .began) {
print("rotation began")
}
if (sender.state == .changed) {
print("rotation changed")
//you could easily make any sprite's rotation equal this amount like so...
//thePlayer.zRotation = -sender.rotation
//convert rotation to degrees...
let rotateAmount = Measurement(value: Double(sender.rotation), unit: UnitAngle.radians).converted(to: .degrees).value
print("\(rotateAmount) degreess" )
}
if (sender.state == .ended) {
print("rotation ended")
}
}
func removeAllGestures(){
//if you need to remove all gesture recognizers with Swift you can do this....
for gesture in (self.view?.gestureRecognizers)! {
self.view?.removeGestureRecognizer(gesture)
}
//this is good to do before a SKScene transitions to another SKScene.
}
func removeAGesture()
{
//To remove a single gesture you can use...
self.view?.removeGestureRecognizer(swipeUpRec)
}
//Fix the gesture recognizing the plusButton
#objc func longPressed(press: UILongPressGestureRecognizer) {
if press.state == .began {
isUserInteractionEnabled = true
let positionInScene = press.location(in: self.view)
let touchedNode = self.atPoint(positionInScene)
plusButton.name = "plusButton"
Kite.physicsBody?.velocity = CGVector(dx: 0, dy: 60.0*3)
print("Pressed on the screen")
if let name = touchedNode.name {
if name == "plusButton" {
print("LONG TAPPED")
}
}
}
}
var start: CGPoint?
var startTime: TimeInterval?
var taps = 0
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("Began")
Kite.physicsBody = SKPhysicsBody(rectangleOf: Kite.size)
Kite.physicsBody?.affectedByGravity = false
//Kite.physicsBody?.velocity = CGVector(dx: 0, dy: 0)
Kite.physicsBody?.applyImpulse(CGVector(dx: 0, dy: 5))
plusButton.name = "plusButton"
plusButton.isUserInteractionEnabled = false
//We figure how to interact with BUTTON in Spritekit
let touch = touches.first
let positionInScene = touch!.location(in: self)
let touchedNode = self.atPoint(positionInScene)
if let name = touchedNode.name {
if name == "plusButton" {
taps += 1
Kite.size = CGSize(width: 200, height: 200)
print("tapped")
print("Taps", taps)
}
}
for touch in touches {
let location:CGPoint = touch.location(in: self.view!)
start = location
startTime = touch.timestamp
}
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
print("Ended")
//Calculating Speed of the Gestures
for touch in touches {
let location:CGPoint = touch.location(in: self.view!)
var dx:CGFloat = location.x - start!.x;
var dy:CGFloat = location.y - start!.y;
var magnitude:CGFloat = sqrt(dx*dx+dy*dy)
//Calculate Time
var dt:CGFloat = CGFloat(touch.timestamp - startTime!)
//Speed = Distance / Time
var speed:CGFloat = magnitude / dt
var speedX:CGFloat = dx/dt
var speedY:CGFloat = dy/dt
print("SpeedY", speedX)
print("SpeedY", speedY)
}
}
so calculating the speed from a vector like dx and dy is
let speed = sqrtf(Float(pow(dx!, 2) + pow(dy!, 2)))
So if you want apply the speed as velocity to an object you need at least one of the directions.
let newDx = 0 // means in no x direction
// next you can change the formula to your new dy to match the given speed
let newDy = sqrtf(pow(speed, 2) - Float(pow(newDx, 2)))
yourNode.physicsBody?.velocity = CGVector(dx: newDx, dy: CGFloat(newDy))

swift skscene touch and hold other action than just touching

So I recently made a swift game with sprite kit and now I am stuck.
I have a character selection screen and I want to show a description for the character if you hold on the character, but if you just touch it you choose it and play with it. I already have the code to play/show the description. I just need to know when to call the corresponding functions and how to differentiate if the node is held or just touched.
Thanks in advance
So, here is how you can do it using SKActions... Also, there is another way using SKAction but I can't really show you all the possibilities :) Anyways, here is the code which does what you want:
import SpriteKit
import GameplayKit
class GameScene: SKScene {
let kLongPressDelayActionKey = "longPressDelay"
let kLongPressStartedActionKey = "longPressStarted"
let kStoppingLongPressActionKey = "stoppingLongPressActionKey"
let desc = SKSpriteNode(color: .white, size: CGSize(width: 150, height: 150))
let button = SKSpriteNode(color: .purple, size: CGSize(width: 150, height: 150))
let other = SKSpriteNode(color: .yellow, size: CGSize(width: 150, height: 150))
override func didMove(to view: SKView) {
addChild(desc)
addChild(button)
addChild(other)
button.position.y = -160
button.name = "button"
desc.alpha = 0.0
desc.name = "description"
other.name = "other"
other.alpha = 0.0
other.position.y = -320
}
private func singleTap(onNode:SKNode){
other.alpha = other.alpha == 0.0 ? 1.0 : 0.0
}
private func startLongPress(withDuration duration:TimeInterval){
//How long does it take before long press is fired
//If user moves his finger of the screen within this delay, the single tap will be fired
let delay = SKAction.wait(forDuration: duration)
let completion = SKAction.run({
[unowned self] in
self.desc.removeAction(forKey: self.kLongPressDelayActionKey)
self.desc.run(SKAction.fadeIn(withDuration: 0.5), withKey: self.kLongPressStartedActionKey)
})
self.desc.run(SKAction.sequence([delay,completion]), withKey: kLongPressDelayActionKey)
}
private func stopLongPress(){
//Fire single tap and stop long press
if desc.action(forKey: kLongPressDelayActionKey) != nil{
desc.removeAction(forKey: kLongPressDelayActionKey)
self.singleTap(onNode: self.other)
//or just stop the long press
}else{
desc.removeAction(forKey: kLongPressStartedActionKey)
//Start fade out action if it isn't already started
if desc.action(forKey: kStoppingLongPressActionKey) == nil {
desc.run(SKAction.fadeOut(withDuration: 0.2), withKey: kStoppingLongPressActionKey)
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
//To stop the long press if we slide a finger of the button
if let touch = touches.first {
let location = touch.location(in: self)
if !button.contains(location) {
stopLongPress()
}
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let location = touch.location(in: self)
if button.contains(location) {
print("Button tapped")
startLongPress( withDuration: 1)
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
stopLongPress()
}
}
You can run the code and you will see that if you tap on the purple button, a yellow box will pop up. If you tap it again, it will hide. Also, if you hold your finger on purple box for 1 second, a white box will fade in. When you release the finger, it will fade out. As an addition, I have implemented touchesMoved, so if you slide your finger of the purple button while blue box is out, it will fade out as well.
So in this example, I have fused long press with single tap. Single tap is considered to be everything that is not long press. Eg, if user holds his finger 0.01 sec, or 0.5 sec, or 0.99 sec, it will be considered as single tap and yellow box will pop out. If you hold your finger for >= 1 second, the long press action will be triggered.
Another way would be to use gesture recognizers...And I will probably make an example for that later :)
And here is how you can do it using gesture recognizers:
import SpriteKit
import GameplayKit
class GameScene: SKScene {
var longPressGesture:UILongPressGestureRecognizer!
var singleTapGesture:UITapGestureRecognizer!
let kLongPressStartedActionKey = "longPressStarted"
let kStoppingLongPressActionKey = "stoppingLongPressActionKey"
let desc = SKSpriteNode(color: .white, size: CGSize(width: 150, height: 150))
let button = SKSpriteNode(color: .purple, size: CGSize(width: 150, height: 150))
let other = SKSpriteNode(color: .yellow, size: CGSize(width: 150, height: 150))
override func didMove(to view: SKView) {
longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(GameScene.longPress(_:)))
singleTapGesture = UITapGestureRecognizer(target: self, action: #selector(GameScene.singleTap(_:)))
self.view?.addGestureRecognizer(longPressGesture)
self.view?.addGestureRecognizer(singleTapGesture)
addChild(desc)
addChild(button)
addChild(other)
button.position.y = -160
button.name = "button"
desc.alpha = 0.0
desc.name = "description"
other.name = "other"
other.alpha = 0.0
other.position.y = -320
}
private func stopLongPress(){
desc.removeAction(forKey: self.kLongPressStartedActionKey)
//Start fade out action if it isn't already started
if desc.action(forKey: kStoppingLongPressActionKey) == nil {
desc.run(SKAction.fadeOut(withDuration: 0.2), withKey: kStoppingLongPressActionKey)
}
}
func singleTap(_ sender:UITapGestureRecognizer){
if sender.state == .ended {
let location = convertPoint(fromView: sender.location(in: self.view))
if self.button.contains(location) {
self.other.alpha = self.other.alpha == 0.0 ? 1.0 : 0.0
}
}
}
func longPress(_ sender: UILongPressGestureRecognizer) {
let longPressLocation = convertPoint(fromView: sender.location(in: self.view))
if sender.state == .began {
if button.contains(longPressLocation) {
desc.run(SKAction.fadeIn(withDuration: 0.5), withKey: self.kLongPressStartedActionKey)
}
}else if sender.state == .ended {
self.stopLongPress()
}else if sender.state == .changed {
let location = convertPoint(fromView: sender.location(in: self.view))
if !button.contains(location) {
stopLongPress()
}
}
}
}

How to subclass a SKSpriteNode and implement touchesBegan?

I'm trying to create a node that has the ability to be swiped offscreen. I tried adding a UIGestureRecognizer the the view but the swipe only works for the first node. I heard there is a way to subclass a spriteNode and in the touchesBegan add the ability to swipe off screen. I can setup a base for a custom sprite node but am unsure of how to give it the ability to be swiped. Here is my code for a custom sprite node:
class CustomNode: SKSpriteNode {
init() {
let texture = SKTexture(imageNamed: customNodeImage)
super.init(texture: texture, color: SKColor.clear, size: texture.size())
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I do have a touchesBegan in my scene but when I touch a node I get a "nil" response when I try and print out the node's name. Here is my scene code:
import SpriteKit
let plankName = "woodPlank"
class PlankScene: SKScene {
var plankWood : Plank?
var plankArray : [SKSpriteNode] = []
override func didMove(to view: SKView) {
plankWood = childNode(withName: "woodPlank") as? Plank
plankWood?.isUserInteractionEnabled = false
let swipeRight : UISwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: #selector(PlankScene.swipedRight))
swipeRight.direction = .right
view.addGestureRecognizer(swipeRight)
}
func swipedRight(sender: UISwipeGestureRecognizer) {
if sender.direction == .right {
let moveOffScreenRight = SKAction.moveTo(x: 400, duration: 0.5)
let nodeFinishedMoving = SKAction.removeFromParent()
let waitForNode = SKAction.wait(forDuration: 0.5)
plankWood?.run(SKAction.sequence([moveOffScreenRight, nodeFinishedMoving]),
completion:{
} )
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
let touchLocation = touch!.location(in: self)
print("\(plankWood?.name)")
if plankWood?.name == plankName {
print("Plank touched")
}
}
override func enumerateChildNodes(withName name: String, using block: #escaping (SKNode, UnsafeMutablePointer<ObjCBool>) -> Void) {
let swipeRight : UISwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: #selector(PlankScene.swipedRight))
swipeRight.direction = .right
view?.addGestureRecognizer(swipeRight)
}
}

UIImage goes where i tap (But i need it so you can ONLY drag it)

I have a UIImage and I linked it into my code and named it "Person"
At the moment where ever I tap on the screen, the UIImage jumps to that position, but I want it so the image can ONLY move if I drag it. Here's my code:
var location = CGPointMake(0, 0)
#IBOutlet var Person: UIImageView!
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch : UITouch = touches.first as UITouch!
location = touch.locationInView(self.view)
Person.center = location
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch : UITouch = touches.first as UITouch!
location = touch.locationInView(self.view)
Person.center = location
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
Person.center = CGPointMake(160, 330)
}
I'd use gesture recognizers - much less to code. Also, I use a "three step" gesture in case there is more than one element to move.
Step #1: The user taps on a subview and a dashed border indicates that the subview is in edit mode.
Step #2: The user pans the subview to a new location in the superview.
Step #3: The user taps anywhere else in the superview (including a new subview to edit it next) to take the first subview out of edit mode.
Subview code:
var _editMode = false
var editMode:Bool {
return _editMode
}
var borderPath = UIBezierPath()
var dashedBorder = CAShapeLayer()
func drawDashedBorder() {
borderPath = UIBezierPath()
borderPath.move(to: CGPoint(x: 3, y: 3))
borderPath.addLine(to: CGPoint(x: self.frame.width-3, y: 3))
borderPath.addLine(to: CGPoint(x: self.frame.width-3, y: self.frame.height-3))
borderPath.addLine(to: CGPoint(x: 3, y: self.frame.height-3))
borderPath.close()
dashedBorder = CAShapeLayer()
dashedBorder.strokeColor = UIColor.white.cgColor
dashedBorder.fillColor = UIColor.clear.cgColor
dashedBorder.lineDashPattern = [2, 2]
dashedBorder.lineDashPhase = 0.0
dashedBorder.lineJoin = kCALineJoinMiter
dashedBorder.lineWidth = 1.0
dashedBorder.miterLimit = 10.0
dashedBorder.path = borderPath.cgPath
let animation = CABasicAnimation(keyPath: "lineDashPhase")
animation.fromValue = 0.0
animation.toValue = 15.0
animation.duration = 0.75
animation.repeatCount = 10000
dashedBorder.add(animation, forKey: "linePhase")
self.addSublayer(dashedBorder)
_editMode = true
}
func removeDashedBorder() {
dashedBorder.removeFromSuperlayer()
_editMode = false
}
This is not production code, so I never did quite figure out how to make the "moving dashed" animation run forever (which may be good, as it could be a performance issue).But 10000 seconds seems to be pretty generous for editing.
Here's the superview code, with the assumption you have imageView1 and imageView2 as subviews:
var editMode = false
override func viewDidLoad() {
super.viewDidLoad()
let tapGesture = UITapGestureRecognizer()
let panGesture = UIPanGestureRecognizer()
tapGesture.numberOfTapsRequired = 1
tapGesture.addTarget(self, action: #selector(editImage))
self.addGestureRecognizer(tapGesture)
panGesture.addTarget(self, action: #selector(moveImage))
self.addGestureRecognizer(panGesture)
}
func editEye(_ recognizer:UITapGestureRecognizer) {
let p = recognizer.location(in: self)
if !editMode {
if (imageView1.layer.hitTest(p) != nil) {
imageView1.drawDashedBorder()
self.editMode = true
} else if (imageView2.layer.hitTest(p) != nil) {
imageView2.drawDashedBorder()
self.editMode = true
}
} else {
if imageView1.editMode {
imageView1.removeDashedBorder()
self.editMode = false
} else if imageView2.editMode {
imageView2.removeDashedBorder()
self.editMode = false
}
}
}
func moveEye(_ recognizer:UIPanGestureRecognizer) {
let p = recognizer.location(in: self)
if imageView1.editMode {
imageView1.position = p
}
if imageView2.editMode {
imageView2.position = p
}
}
This may not fit your exact needs, but the combination of tap & pan over tap for editing seems more natural. I'm sure that at the very least, doing a "silent" tap & pan (where it appears to the user they are simply dragging something around but they still have to tap on it first) is a better fit.
You can do this with touchesMoved event. But my recommendation is to use UIPanGestureRecognizer.
Declare a UIPanGestureRecognizer object and add it to your image view.
User interaction enable for image view.
Follow this nice apple documented sample code https://developer.apple.com/library/content/samplecode/Touches/Introduction/Intro.html#//apple_ref/doc/uid/DTS40007435
in the touchesBegan method: check if the touch hit the UIImageView and set a global boolean variable to true. Set it to false on touchesEnded, and check in touchesMoved if the variable is true before you move the UIImageview
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch : UITouch = touches.first as UITouch!
location = touch.locationInView(self.view)
if(person.frame.origin.x<location.x&&person.frame.origin.x+person.frame.size.width>location.x{
// x value for touch is inside uiimageview
if(person.frame.origin.y<location.y&&person.frame.origin.y+person.frame.size.height>location.y{
//x&y value inside
personHit=true
}
}
// Person.center = location
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
if(personHit){
let touch : UITouch = touches.first as UITouch!
location = touch.locationInView(self.view)
Person.center = location
}
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
personHit=false
}
Simple Approach to fix your problem:
#IBOutlet weak var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
imageView.isUserInteractionEnabled = true
let panGestureRecognizer = UIPanGestureRecognizer(target:self, action: #selector(panHandler(recognizer:)))
imageView.addGestureRecognizer(panGestureRecognizer)
}
func panHandler(recognizer:UIPanGestureRecognizer) {
let translation = recognizer.translation(in: view)
recognizer.view!.center = CGPoint(x: recognizer.view!.center.x + translation.x, y: recognizer.view!.center.y + translation.y)
recognizer.setTranslation(CGPoint.zero, in: view)
}

Resources