How to track time of finger on screen swift - ios

I`m here because after weeks of trying different solutions and don't come with the right answer and functional in-app I am exhausted.
I need to track the time of finger on-screen and if a finger is on screen longer than 1 sec I need to call function. But also if now user is performing gestures like a pan or pinch function must be not called.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
let touch = touches.first
guard let touchLocation = touch?.location(in: self) else {return }
let tile: Tile?
switch atPoint(touchLocation){
case let targetNode as Tile:
tile = targetNode
case let targetNode as SKLabelNode:
tile = targetNode.parent as? Tile
case let targetNode as SKSpriteNode:
tile = targetNode.parent as? Tile
default:
return
}
guard let tile = tile else {return }
paint(tile: tile)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
}
private func paint(tile: Tile){
let col = tile.column
let row = tile.row
let colorPixel = gridAT(column: col, row: row)
if !tile.isTouchable {
}else {
if !selectedColor.isEmpty {
if tile.mainColor == selectedColor {
tile.background.alpha = 1
tile.background.color = UIColor(hexString:selectedColor)
tile.text.text = ""
tile.backgroundStroke.color = UIColor(hexString:selectedColor)
uniqueColorsCount[selectedColor]?.currentNumber += 1
didPaintCorrect(uniqueColorsCount[selectedColor]?.progress ?? 0)
colorPixel?.currentState = .filledCorrectly
tile.isTouchable = false
}
else if tile.mainColor != selectedColor {
tile.background.color = UIColor(hexString:selectedColor)
tile.background.alpha = 0.5
colorPixel?.currentState = .filledIncorrectly
}
}
}
}

Here is a small example I created for you with my suggestion of using UILongPressGestureRecognizer as it seems easier to manage for your situation than processing touchesBegin and touchesEnded
You can give it the minimum time the user needs to tap so it seems perfect for your requirement.
You can read more about it here
First I just set up a basic UIView inside my UIViewController with this code and add a long tap gesture recognizer to it:
override func viewDidLoad() {
super.viewDidLoad()
// Create a basic UIView
longTapView = UIView(frame: CGRect(x: 15, y: 30, width: 300, height: 300))
longTapView.backgroundColor = .blue
view.addSubview(longTapView)
// Initialize UILongPressGestureRecognizer
let longTapGestureRecognizer = UILongPressGestureRecognizer(target: self,
action: #selector(self.handleLongTap(_:)))
// Configure gesture recognizer to trigger action after 2 seconds
longTapGestureRecognizer.minimumPressDuration = 2
// Add gesture recognizer to the view created above
view.addGestureRecognizer(longTapGestureRecognizer)
}
This gives me something like this:
Next, to get the location of the tap, the main question to ask yourself is - Where did the user tap in relation to what view ?
For example, let's say the user taps here:
Now we can ask what is location of the tap in relation to
The blue UIView - It is approx x = 0, y = 0
The ViewController - It is approx x = 15, y = 30
The UIView - It is approx x = 15, y = 120
So based on your application, you need to decide, in relation to which view do you want the touch.
So here is how you can get the touch based on the view:
#objc
private func handleLongTap(_ sender: UITapGestureRecognizer)
{
let tapLocationInLongTapView = sender.location(in: longTapView)
let tapLocationInViewController = sender.location(in: view)
let tapLocationInWindow = sender.location(in: view.window)
print("Tap point in blue view: \(tapLocationInLongTapView)")
print("Tap point in view controller: \(tapLocationInViewController)")
print("Tap point in window: \(tapLocationInWindow)")
// do your work and function here
}
For same touch as above image, I get the following output printed out:
Tap point in blue view: (6.5, 4.5)
Tap point in view controller: (21.5, 34.5)
Tap point in window: (21.5, 98.5)

Related

How can I make an action to occur when two buttons are pressed at the same time?

I want my character to jump whenever I press two buttons at the same time. I've already tried this:
if rightButton.contains(location) && leftButton.contains(location) {
character.physicsBody?.applyImpulse(CGVector(dx: 0, dy: 50))
}
One approach would be:
In your functions that detects the interaction with the button prepare it with a boolean.
Then in your Update function, use a timer to add a range of time where we can say that both buttons are pressed at the same time (100 ms for example).
I'll let you here some pseudocode that I hope it helps.
func RightBtnClick()->Void{
rightBtnPressed = true
}
func LeftBtnClick()->Void{
leftBtnPressed = true
}
func Start()->Void{
rightBtnTimer = 0
leftBtnTimer = 0
}
func Update(deltatime ms:float)->Void{
if(rightBtnPressed){
rightBtnTimer += ms;
if(rightBtnTimer>100){
rightBtnTimer = 0
rightBtnPressed=false
}
}
if(leftBtnPressed){
leftBtnTimer += ms;
if(leftBtnTimer>100){
leftBtnTimer = 0
leftBtnPressed=false
}
}
// Lastly let's check if both are pressed.
if(leftBtnPressed && rightBtnPressed){
DoStuff()
}
}
First of all, make sure in GameViewController.swift you have multitouch enabled.
class GameViewController: UIViewController
{
override func viewDidLoad()
{
super.viewDidLoad()
// ...
if let view = self.view as! SKView?
{
// ...
view.isMultipleTouchEnabled = true
}
}
}
In GameScene give name to your buttons. On tap we will create a list of every node your fingers touched that has a name. If the list contains both right and left button, it means he pressed both at the same time.
class GameScene: SKScene
{
override func didMove(to view: SKView)
{
// add name to each button
left_button.name = "left_button"
right_button.name = "right_button"
}
func buttons_touched(_ touches: Set<UITouch>) -> [String]
{
// get the list of buttons we touched on each tap
var button_list : [String] = []
for touch in touches
{
let positionInScene = touch.location(in: self)
let touchedNode = self.nodes(at: positionInScene)
let buttons = touchedNode.compactMap { (node) -> String in
node.name ?? ""
}.filter({$0 != ""})
button_list += buttons
}
return button_list
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
{
let buttons_tapped = buttons_touched(touches)
if buttons_tapped.contains("right_button") && buttons_tapped.contains("left_button")
{
// jump code
}
}
}
You can simulate multitouch inside Simulator by holding Option button.

How to stop the movement of a sprite when touched and moved out of the node ( button ) boundaries

I am working on a simple runner game, I have just 3 shapenodes a player node which is just a shapenode (redbox, 30 x 30) with left/right buttons basically two shapenodes.
I have managed to set flags and increment player node's position X and move it left and right with buttons in touchesBegan method and stop the movement in touchesEnded method everything works fine.
The problem is that if I touch lets say right button, the player moves to the right as expected. But if I touch and move my finger out of the button boundaries in any direction, the player keeps moving constantly as long as I touch that button again then it stops. Otherwise it keeps moving and the other button does not stop it as well.
I used touchesMoved method to stop the movement of the player when I move my finger but this does not fix the issue since touchesMoved method triggers with the slightest movement of the touch even when my finger slightly shakes.
I want the touchesMoved method called when my finger is moved off the button, not on the button.
How can I stop the movement of a sprite when touched and moved out of the node ( button ) boundaries?
You have 3 touch events:
touchesBegan; check if user tap is on one the move buttons (if yes - move player, if not - return)
touchesMoved; check if the user tap is still inside the boundaries of the move button (if yes - continue moving player, if not - stop movement)
touchesEnded; stop player movement
You can solve your problem by checking if the user finger is over the button area.
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?)
{
// nothing here
let touch = touches.first
let position_in_scene = touch!.location(in: self)
check_button(position: position_in_scene)
}
func check_button(position : CGPoint)
{
if right_button.contains(position)
{
// the tap was inside button boundaries
player.move_left()
}
else
{
// the tap was outside button boundaries
player.stop()
}
}
I have this code running :
class GameScene: SKScene {
var player = SKSpriteNode()
var left = SKSpriteNode()
var right = SKSpriteNode()
var jumpbtn = SKSpriteNode()
var leftbtnPressed = false
var rightbtnPressed = false
var jump = false
var hSpeed: CGFloat = 2.0
override func didMove(to view: SKView) {
let border = SKPhysicsBody(edgeLoopFrom: self.frame)
border.friction = 0
self.physicsBody = border
jumpbtn = self.childNode(withName: "jumpbtn") as! SKSpriteNode
player = self.childNode(withName: "r1") as! SKSpriteNode
left = self.childNode(withName: "leftbtn") as! SKSpriteNode
right = childNode(withName: "rightbtn") as! SKSpriteNode
}//didmove to view
func moveRight(){
player.position = CGPoint(x: player.position.x + hSpeed, y: player.position.y)
}
func moveLeft (){
player.position = CGPoint(x: player.position.x - hSpeed, y: player.position.y)
}
func movement (){
if rightbtnPressed == true {
moveRight()
}
if leftbtnPressed == true {
moveLeft()
}
}//movement
func CheckButton(position : CGPoint) {
if right.contains(position){
rightbtnPressed = true
}
if left.contains(position) {
leftbtnPressed = true
}
else {
leftbtnPressed = false
rightbtnPressed = false
}
}// checkbutton
func jumping (){
if jump == true {
player.physicsBody?.applyImpulse(CGVector(dx:0, dy: 5.0))
}
}// jumping
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let positionINScene = touch.location(in: self)
let touchednode = self.atPoint(positionINScene).name
if touchednode == "rightbtn"{
rightbtnPressed = true
}
if touchednode == "leftbtn"{
leftbtnPressed = true
}
if touchednode == "jumpbtn"{
jump = true
jumping()
}
}
}//touchesbegan
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
let positionInScene = touch?.location(in: self)
CheckButton(position: positionInScene!)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let positionINScene = touch.location(in: self)
let touchednode = self.atPoint(positionINScene)
if touchednode.name == "rightbtn"{
rightbtnPressed = false
}
if touchednode.name == "leftbtn"{
leftbtnPressed = false
}
if touchednode.name == "jumpbtn" {
jump = false
}
}
}//touchesEnded
override func update(_ currentTime: TimeInterval) {
movement()
}
When i touch either left or right button the player starts moving as expected but the problem is while the player is moving let say to the right if i touch the jump button the player pauses moving where it should be moving and jumping
in simple words i can not move and jump at the same time

Detect which `View` was clicked on a `Cell`

i have three UIImageView on a single Cell when i click on any of the UIImageView on the cell i want to detect which one was clicked on onCellSelection, without placing a UITapGestureRecognizer on each UIImageview
func SocialViewRow(address: SocialMedia)-> ViewRow<SocialMediaViewFile> {
let viewRow = ViewRow<SocialMediaViewFile>() { (row) in
row.tag = UUID.init().uuidString
}
.cellSetup { (cell, row) in
// Construct the view
let bundle = Bundle.main
let nib = UINib(nibName: "SocialMediaView", bundle: bundle)
cell.view = nib.instantiate(withOwner: self, options: nil)[0] as? SocialMediaViewFile
cell.view?.backgroundColor = cell.backgroundColor
cell.height = { 50 }
print("LINK \(address.facebook?[0] ?? "")")
cell.view?.iconOne.tag = 90090
//self.itemDetails.activeURL = address
let openFace = UITapGestureRecognizer(target: self, action: #selector(QuickItemDetailVC.openFace))
let openT = UITapGestureRecognizer(target: self, action: #selector(QuickItemDetailVC.openTwit))
let you = UITapGestureRecognizer(target: self, action: #selector(QuickItemDetailVC.openYouYub))
cell.view?.iconOne.addGestureRecognizer(openFace)
cell.view?.iconTwo.addGestureRecognizer(openT)
cell.view?.iconThree.addGestureRecognizer(you)
cell.frame.insetBy(dx: 5.0, dy: 5.0)
cell.selectionStyle = .none
}.onCellSelection() {cell,row in
//example
//print(iconTwo was clicked)
}
return viewRow
}
Using UITapGestureRecogniser (or UIButton) would be a better approach. These classes intended for tasks like this.
If you still want to use different approach, add method to your cell subclass (replace imageView1, imageView2, imageView3 with your own properties)
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let point = touch.location(in: view)
if imageView1.frame.containsPoint(point) {
// code for 1st image view
} else if imageView2.frame.containsPoint(point) {
// code for 2nd image view
} else if imageView3.frame.containsPoint(point) {
// code for 3rd image view
}
}
Docs:
location(ofTouch:in:)
contains(_ point: CGPoint)
Override the touchesbegan function. This method is called every time the user touches the screen. Every time it is called, check to see if the touches began in the same location an image is.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first!
let location = touch.location(in: self)
//check here, include code that compares the locations of the image
}
Location will be a CGPoint. You should be able to get the CGPoints for the bounds of your images and then determine if the touchBegan in those bounds. If you want to include the entire path the user touched, there are ways to do that too but the beginning touch should be sufficient for what you want.

How can I create a finite, scrollable background in SpriteKit?

I'm looking to have a scrollable background in Sprite Kit. I've had a go with some of the other solutions available online, but they were implementing infinite scrolling backgrounds, and I haven't been able to adapt the code to my needs.
Here is some sample code which I've got to try and get the background moving (without the detection of reaching the end of the background) - but it's very choppy and not smooth at all.
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touchLocation = touches.first?.location(in: self), let node = nodes(at: touchLocation).first {
if node.name != nil {
if node.name == "background" {
background.position = touchLocation
}
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touchLocation = touches.first?.location(in: self), let node = nodes(at: touchLocation).first {
if node.name != nil {
if node.name == "background" {
background.position = touchLocation
}
}
}
}
The image below demonstrates what I'm trying to achieve - I want the code to detect when you've reached the end of the background, and to prevent you from moving it any further.
So, taking #KnightOfDragon's comment into account about needing to set maximum and minimum X coordinate values for the background, I was able to solve my own question. I already had swipe left/right recognisers in my code (for another purpose in my game), and I was able to reuse these to fulfil my needs. Code is as follows:
In didMove():
swipeRightRec.addTarget(self, action: #selector(self.swipedRight) )
swipeRightRec.direction = .right
self.view!.addGestureRecognizer(swipeRightRec)
swipeLeftRec.addTarget(self, action: #selector(self.swipedLeft) )
swipeLeftRec.direction = .left
self.view!.addGestureRecognizer(swipeLeftRec)
And then these functions:
#objc func swipedRight() {
if background.position.x + 250 > maxBackgroundX {
let moveAction = SKAction.moveTo(x: maxBackgroundX, duration: 0.3)
background.run(moveAction)
} else {
let moveAction = SKAction.moveTo(x: background.position.x + 250, duration: 0.3)
background.run(moveAction)
}
}
#objc func swipedLeft() {
if background.position.x - 250 < minBackgroundX {
let moveAction = SKAction.moveTo(x: minBackgroundX, duration: 0.3)
background.run(moveAction)
} else {
let moveAction = SKAction.moveTo(x: background.position.x - 250, duration: 0.3)
background.run(moveAction)
}
}
Yes this means that the background moves a set amount each time you swipe, no matter how big the swipe is, but it is exactly what I required for my game. I hope this helps someone else who needs the same thing!

RemoveAction when swiped from node

I have this sene with few nodes in it. It is circle inside circle inside circle. Pressing on smallest circle inside, I have this animation made with few SKActions.
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
/* Called when a touch begins */
let growOut = SKAction.scaleTo(1.2, duration: 0.3)
let growIn = SKAction.scaleTo(1.0, duration: 0.5)
let glowOut = SKAction.fadeAlphaTo(0.5, duration: 0.3)
let glowIn = SKAction.fadeAlphaTo(1, duration: 0.5)
let sOut = SKAction.group([glowOut, growOut])
let sIn = SKAction.group([glowIn, growIn])
let circleTouched = SKAction.sequence([sOut, sIn])
let circleRepeat = SKAction.repeatActionForever(circleTouched)
for touch in touches {
let location = (touch as! UITouch).locationInNode(self)
if let theCircle = nodeAtPoint(location) as SKNode?{
if theCircle.name == "SmallCircle" {
theCircle.runAction(circleRepeat, withKey: "circleTouched")
}
}
}
}
When touch ends, I remove this action like so:
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
let growIn = SKAction.scaleTo(1.0, duration: 0.5)
let glowIn = SKAction.fadeAlphaTo(1, duration: 0.5)
let sIn = SKAction.group([glowIn, growIn])
for touch in touches {
let location = (touch as! UITouch).locationInNode(self)
if let theCircle = nodeAtPoint(location) as SKNode?{
theCircle.runAction(sIn)
theCircle.removeActionForKey("circleTouched")
}
}
}
But when I move my finger out of this circle with actions on it, it keeps on playing. I've tried to fix it with touchesMoved function, but it acts kind of strange for me.
override func touchesMoved(touches: Set, withEvent event: UIEvent) {
let growIn = SKAction.scaleTo(1.0, duration: 0.5)
let glowIn = SKAction.fadeAlphaTo(1, duration: 0.5)
let sIn = SKAction.group([glowIn, growIn])
let circleRepeat = SKAction.repeatActionForever(circleTouched)
for touch in touches {
let location = (touch as! UITouch).locationInNode(self)
if let theCircle = nodeAtPoint(location) as SKNode?{
if theCircle.name != "SmallCircle" {
println("I'M MOVING FROM NODE!!!")
theCircle.runAction(sIn)
theCircle.removeActionForKey("circleTouched")
}
}
}
}
So I receive this "I'M OUT OF THE NODE" signal, but action don't stop.
Where am I wrong? The same code works for touchesEnded function.
The problem happens because of this one
if let theCircle = nodeAtPoint(location) as SKNode?
Everytime you move your mouse, "theCircle" resets. For example, for the first time, you click the circle, "theCircle" is the circle you clicked, hence the animation is attached to it. For the second time, say, you clicked the background, this time "theCircle" is the background, so it does not have the animation you set, therefore there is no way to remove "the animation".
The solution is, you declare the circle as a scope level variable, usually inside the class, at the top:
var smallCircle: SKSpriteNode!
Then in didMoveToView(view: SKView), configure the circle(if you use .sks):
smallCircle = childNodeWithName("the circle name") as! SKSpriteNode
smallCircle.name = "SmallCircle"
This time, you can point to the circle in touchMoved:
for touch in (touches as! Set<UITouch>) {
let location = touch.locationInNode(self)
if let theCircle = nodeAtPoint(location) as SKNode?{
if theCircle.name != "SmallCircle" {
smallCircle.runAction(sIn)
smallCircle.removeActionForKey("circleTouched")
}
}
}
At last, you will find the animation stopped.

Resources