Basically, I want to make a mobile game using a custom hand gesture. The gesture is kind of like rubbing the screen, from left to right and from right to left.
If the gesture moves from the left to right, this will call a method and add a point. And once the gesture rubs from right to left, the user can also get one point.
But the problem is, when I use Swipe gesture recognizer, I have to release my finger from the screen, in order to call the method. If I just do the rub gesture, I am unable to call the method to add points.
Instead, I try the touchesBegan, touchesMoved method to detect the finger's position. However, touchesMoved will create many points to compare with the starting point, which leads to calling the methods many times instead of once.
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches
{
self.touchStartPoint = touch.locationInView(self.myView).x
}
}
override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches
{
self.touchOffsetPoint = touch.locationInView(self.myView).x - touchStartPoint
if tempTouchOffsetPoint < touchOffsetPoint
{
var xValueIncreaseArray: NSMutableArray = []
xValueIncreaseArray.addObject(touchOffsetPoint)
var maxValue: Double = (xValueIncreaseArray as AnyObject).valueForKeyPath("#max.self") as Double
println("\(maxValue)")
if maxValue - Double (self.touchStartPoint) > 50
{
println("right")
}
println("right")
}
else if tempTouchOffsetPoint > touchOffsetPoint
{
/* var xValueDecreaseArray: NSMutableArray = []
xValueDecreaseArray.addObject(touchOffsetPoint)*/
println("left")
}
else if tempTouchOffsetPoint == touchOffsetPoint
{
println("Remain")
}
tempTouchOffsetPoint = touchOffsetPoint
}
Are there any ways to detect the rubbing gesture? And every time the finger turns its direction, it will call a method only ONCE to add a score to user? Thank you so much!
This works for me:
let deadZone:CGFloat = 10.0
var previousTouchPoint: CGFloat!
var isMovingLeft:Bool? = nil
override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
let point = touches.anyObject()?.locationInView(self)
if let newPoint = point?.x {
if previousTouchPoint == nil {
println("Started")
previousTouchPoint = newPoint
} else {
// Check if they have moved beyond the dead zone
if (newPoint < (previousTouchPoint - deadZone)) || (newPoint > (previousTouchPoint + deadZone)) {
let newDirectionIsLeft = newPoint < previousTouchPoint
// Check if the direction has changed
if isMovingLeft != nil && newDirectionIsLeft != isMovingLeft! {
println("Direction Changed: Point")
}
println((newDirectionIsLeft) ? "Moving Left" : "Moving Right")
isMovingLeft = newDirectionIsLeft
previousTouchPoint = newPoint
}
}
}
}
override func touchesCancelled(touches: NSSet!, withEvent event: UIEvent!) {
previousTouchPoint = nil
isMovingLeft = nil
}
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
previousTouchPoint = nil
isMovingLeft = nil
}
Related
So I've been messing around trying to get the coordinates of touches on the screen. So far I can get the coordinates of one touch with this:
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
let touch = touches.anyObject()! as UITouch
let location = touch.locationInView(self.view)
println(location)
}
But when touching with two fingers I only get the coordinates of the first touch. Multi-touch works (I tested with this little tutorial: http://www.techotopia.com/index.php/An_Example_Swift_iOS_8_Touch,_Multitouch_and_Tap_Application). So my question is, how do I get the coordinates of the second (and third, fourth...) touch?
** Updated to Swift 4 and Xcode 9 (8 Oct 2017) **
First of all, remember to enable multi-touch events by setting
self.view.isMultipleTouchEnabled = true
in your UIViewController's code, or using the appropriate storyboard option in Xcode:
Otherwise you'll always get a single touch in touchesBegan (see documentation here).
Then, inside touchesBegan, iterate over the set of touches to get their coordinates:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self.view)
print(location)
}
}
the given touches argument is a set of detected touches.
You only see one touch because you select one of the touches with :
touches.anyObject() // Selects a random object (touch) from the set
In order to get all touches iterate the given set
for obj in touches.allObjects {
let touch = obj as UITouch
let location = touch.locationInView(self.view)
println(location)
}
You have to iterate over the different touches. That way you can access every touch.
for touch in touches{
//Handle touch
let touchLocation = touch.locationInView(self.view)
}
In Swift 1.2 this has changed, and touchesBegan now provides a Set of NSObjects.
To iterate through them, cast the touches collection as a Set of UITouch objects as follows:
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
var touchSet = touches as! Set<UITouch>
for touch in touchSet{
let location = touch.locationInView(self.view)
println(location)
}
}
For Swift 3, based on #Andrew's answer :
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let touchSet = touches
for touch in touchSet{
let location = touch.location(in: self.view)
print(location)
}
}
EDIT, My bad, that's not answering your question, had the same problem and someone linked me to this previous answer :
Anyway, I had to change few things to make it works in swift 3, here is my current code :
var fingers = [String?](repeating: nil, count:5)
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
for touch in touches{
let point = touch.location(in: self.view)
for (index,finger) in fingers.enumerated() {
if finger == nil {
fingers[index] = String(format: "%p", touch)
print("finger \(index+1): x=\(point.x) , y=\(point.y)")
break
}
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesMoved(touches, with: event)
for touch in touches {
let point = touch.location(in: self.view)
for (index,finger) in fingers.enumerated() {
if let finger = finger, finger == String(format: "%p", touch) {
print("finger \(index+1): x=\(point.x) , y=\(point.y)")
break
}
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
for touch in touches {
for (index,finger) in fingers.enumerated() {
if let finger = finger, finger == String(format: "%p", touch) {
fingers[index] = nil
break
}
}
}
}
I still have a little problem but I think it's linked to my GestureRecognizer in my code.
But that should do the trick.
It will print you the coordinate of each point in your consol.
In Swift 3,4
Identify touch pointer by its hash:
// SmallDraw
func pointerHashFromTouch(_ touch:UITouch) -> Int {
return Unmanaged.passUnretained(touch).toOpaque().hashValue
}
I'm trying to do something (print "Swiped") IF the velocity of a swipe is greater than a specified criteria. Based on researching I've come up with this code. "Began" gets printed whenever I touch the screen, but "Ended" and "Swiped" only print when I first tap and hold the screen, then drag, then lift up (a rather slow process). I hope i can just flick the screen and my text gets printed, but right now it will only print if i take my time with the swipe process. Not exactly a quick swipe.. you know? Hopefully this makes sense!
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
print("Began")
for touch in touches {
let location:CGPoint = touch.locationInView(self.view!)
start = location
startTime = touch.timestamp
}
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
print("Ended")
for touch in touches {
let location:CGPoint = touch.locationInView(self.view!)
var dx:CGFloat = location.x - start!.x;
var dy:CGFloat = location.y - start!.y;
var magnitude:CGFloat = sqrt(dx*dx+dy*dy)
if (magnitude >= kMinDistance) {
print("OK")
var dt:CGFloat = CGFloat(touch.timestamp - startTime!)
if (dt > kMinDuration) {
var speed:CGFloat = magnitude / dt;
if (speed >= kMinSpeed && speed <= kMaxSpeed) {
print("Swiped")
}
}
}
}
}
To avoid unexpected game behaviour I'd like to know coordinates of all fingers on the touchscreen at any given moment. Is there a way of doing this?
For example get coordinates of the fist finger when second is taken off from the screen.
Thanks in advance!
Here's the solution based on trojanfoe's idea.
import SpriteKit
class GameScene : SKScene {
struct fingerTrackData {
var fingerID : Int // used to identify fingers in other part of the code
var lastLocation : CGPoint // location of each finger
}
// array that stores information about each finger coordinates
var fingerTracks : [fingerTrackData] = []
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
// getAvailableFingerID() returns available fingerID to avoid ID duplication
self.fingerTracks.append(fingerTrackData(fingerID: self.getAvailableFingerID(), lastLocation: location))
}
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
self.updateFingerTracks(location)
}
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
// deregistering unnecessary finger tracks
self.fingerTracks.removeAtIndex(self.getClosestFingerArrayIndexByLocation(location))
}
}
override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
// touchesCancelled occurs e.g. when more than 5 fingers on iPhone is on the screen
// Deregistering all tracks here
self.fingerTracks.removeAll()
}
override func update(currentTime: NSTimeInterval) {
// to see in console what is actually happening in array
if fingerTracks.count > 0 {
for fingerTrack in self.fingerTracks {
print("Finger: \(fingerTrack.fingerID) Location: \(fingerTrack.lastLocation)")
}
} else {
print("---")
}
}
/*
* Below are some helper functions for convenience
*/
func getClosestFingerArrayIndexByLocation (location: CGPoint) -> Int {
var fingerTrackKeyToUpdate : Int = 0
var shortestDistance : CGFloat = 100500 // some big number that is much bigger than screen size
for (fingerTrackKey, fingerTrack) in self.fingerTracks.enumerate() {
// calculating distance from previous record for each finger
let calculatedDistance = sqrt(pow(fingerTrack.lastLocation.x - location.x,2) + pow(fingerTrack.lastLocation.y - location.y,2))
// shortest one gives us finger ID
if calculatedDistance < shortestDistance {
shortestDistance = calculatedDistance
fingerTrackKeyToUpdate = fingerTrackKey
}
}
return fingerTrackKeyToUpdate
}
func updateFingerTracks(location: CGPoint) {
let closestFingerArrayIndex = self.getClosestFingerArrayIndexByLocation(location)
let fingerIDToUpdate = self.fingerTracks[closestFingerArrayIndex].fingerID
self.fingerTracks[closestFingerArrayIndex] = fingerTrackData(fingerID: fingerIDToUpdate, lastLocation: location)
}
func getClosestFingerIdByLocation(location: CGPoint) -> Int {
return self.fingerTracks[self.getClosestFingerArrayIndexByLocation(location)].fingerID
}
func getAvailableFingerID() -> Int {
var availableFingerID : Int = 0
if self.fingerTracks.count > 0 {
var checkAgain = true
while checkAgain {
checkAgain = false
for fingerTrack in self.fingerTracks {
if availableFingerID == fingerTrack.fingerID {
checkAgain = true
availableFingerID++
}
}
}
}
return availableFingerID
}
}
Thanks all for your assistance!
class GameScene : SKScene {
// stores all touches at any moment
var touchTracks : [UITouch] = []
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
self.touchTracks.append(touch)
}
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
var touchIndexToRemove : Int?
for i in 0..<self.touchTracks.count {
if self.touchTracks[i] == touch {
touchIndexToRemove = i
}
}
self.touchTracks.removeAtIndex(touchIndexToRemove!)
}
}
override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
// touchesCancelled occurs e.g. when more than 5 fingers on iPhone is on the screen
// Deregistering all tracks here
self.touchTracks.removeAll()
}
}
I have the following segment of code in which I want a tap to cause the ball to move in my screen.
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
moveBallUp()
moveBallLeft()
moveBallRight()
}
func moveBallUp() -> Void {
ball.physicsBody?.applyImpulse(CGVectorMake(0, impulseFactor))
}
func moveBallLeft() -> Void {
var random = -1 * CGFloat(Float(arc4random()))
ball.physicsBody?.applyImpulse(CGVectorMake(random % impulseFactor, 0))
}
func moveBallRight() -> Void {
var random = CGFloat(Float(arc4random()))
ball.physicsBody?.applyImpulse(CGVectorMake(random % impulseFactor, 0))
}
How do I make it such that only taps on the SKSpriteNode variable "ball" will cause the ball to move?
You can set ball's name (ballNode.name = "ball") and access it like this:
touchesBegan:
let location = (touch as UITouch).locationInNode(self)
if let ball = self.nodeAtPoint(location)
if ball.name == "ball" {
//move the ball
}
}
Another way would be using subclassing SKSpriteNode (and implementing touchesBegan method) like this:
class Ball : SKSpriteNode
{
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
//do your stuff here
}
}
If you are subclassing SKSpriteNode you have to set userInteractionEnabled = true. you can do this in ball's init method or after you create Ball object.
So I am super new to swift and iOS development but not totally new to programming, and was just going through some tutorials, but basically my code looks like this:
import SpriteKit
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
let circle = SKShapeNode(circleOfRadius: 30.0)
circle.position = CGPointMake(100, 200)
addChild(circle)
}
override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
for touch in touches {
let location = touch.locationInNode(self)
let touchedNode = nodeAtPoint(location)
touchedNode.position = location;
}
}
}
When I build this it recognizes and moves the circle when I drag it, but only for about 30 pixels and then I have to touch it again to move it. What am I doing wrong here?
I might be wrong but I believe your finger is leaving the node limits and enters the background. I would set it up as such:
// First make the shape available throughout the code and make sure you have a place to save the touch event for later use.
var circle: SKShapeNode!
var circleTouch: UITouch?
// Create the Shape
override func didMoveToView(view: SKView) {
circle = SKShapeNode(circleOfRadius: 30.0)
circle.position = CGPointMake(100, 200)
addChild(circle)
}
// When a finger is placed on the screen, check if it's in the circle, if it is, keep that touch even in memory so we can reference it later because other fingers could touch other things in the scene.
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch in touches {
if nodeAtPoint(touch.locationInNode(self)) == circle {
circleTouch = touch as? UITouch
}
}
}
// Check if the touch event that is moving is the one that anchored the circle to our finger, if yes, move the circle to the position of the finger.
override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
for touch in touches {
if circleTouch != nil {
if touch as UITouch == circleTouch! {
let location = touch.locationInNode(self)
circle.position = location
}
}
}
}
// Clean up the touch event when the finger anchoring the circle is raised from the screen.
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
for touch in touches {
if circleTouch != nil {
if touch as UITouch == circleTouch! {
circleTouch = nil
}
}
}
}
You can also use if let statements in there but I checked for nil instead for clarity.
Tokuriku, thanks so much, you we're not quite right but it got me to the eventual answer, here it is
import SpriteKit
class GameScene: SKScene {
var circleTouch: UITouch?
override func didMoveToView(view: SKView) {
/* Setup your scene here */
let circle = SKShapeNode(circleOfRadius: 40.0)
circle.fillColor = UIColor.blackColor()
circle.position = CGPoint(x: size.width * 0.5, y: size.height * 0.2)
circle.name = "userCircle"
addChild(circle)
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch in touches {
if nodeAtPoint(touch.locationInNode(self)).name == "userCircle" {
circleTouch = touch as? UITouch
}
}
}
override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
for touch in touches {
if circleTouch != nil {
if touch as UITouch == circleTouch! {
let location = touch.locationInNode(self)
let touchedNode = nodeAtPoint(location)
touchedNode.position = location
}
}
}
}
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
for touch in touches {
if circleTouch != nil {
if touch as UITouch == circleTouch! {
circleTouch = nil
}
}
}
}