How to detect the location of my finger through in a scrollview or webview in Swift? (using touchesMoved if possible) - ios

I want to print the location of the user's finger on a webview.
Using this (below) doesn't work on a webview
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let position = touch.location(in: self.view) // even when i replace this part with UIWebview
print(position.x)
print(position.y)
}
}

I think that creating a UIPanGestureRecognizer should solve it, add the following code into viewDidLoad() method:
override func viewDidLoad() {
// ...
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(webViewtouchMoved(panGesture:)))
panGesture.delegate = self
webView.addGestureRecognizer(panGesture)
// ...
}
webViewtouchMoved(panGesture:) method:
func webViewtouchMoved(panGesture: UIPanGestureRecognizer) {
if panGesture.state == .began || panGesture.state == .changed {
let position = panGesture.location(in: view)
print(position.x)
print(position.y)
}
}
also, you should add this extension for the desired ViewController:
// change 'ViewController' to your class name:
extension ViewController: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
hope this helped.

Related

How to detect that multiple UIGestureRecognizers end?

in my view I have multiple gesture recognizers (UIPanGestureRecognizer, UIPinchGestureRecognizer and UIRotationGestureRecognizer) and I allowed them to detect touches simultaneously:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer.view != otherGestureRecognizer.view {
return false
}
return true
}
The problem with this approach is that I am no longer able to detect when all the 3 gestures end. I cannot use
if gesture.state == .ended || gesture.state == .cancelled {
}
bacause this is valid only for a gesture recognizer and not for the all 3.
Any idea if there an api to detect when all active recognizers end?
Thanks
Solution
This works but it is very ugly: basically I keep track when all three gestures recognizers end and prevent to detect the end multiple times as the recognizers callback can be called in any order:
class SCCanvasViewController: UIViewController {
var gesturesAlreadyEnded = false
lazy var panGestureRecognizer: UIPanGestureRecognizer = {
let gr = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
gr.delegate = self
return gr
}()
lazy var pinchGestureRecognizer: UIPinchGestureRecognizer = {
let gr = UIPinchGestureRecognizer(target: self, action: #selector(handlePinch(_:)))
gr.delegate = self
return gr
}()
lazy var rotateGestureRecognizer: UIRotationGestureRecognizer = {
let gr = UIRotationGestureRecognizer(target: self, action: #selector(handleRotation(_:)))
gr.delegate = self
return gr
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addGestureRecognizer(panGestureRecognizer)
view.addGestureRecognizer(pinchGestureRecognizer)
view.addGestureRecognizer(rotateGestureRecognizer)
}
func isGestureEnded(gesture: UIGestureRecognizer) -> Bool {
return gesture.state == .ended || gesture.state == .cancelled || gesture.state == .failed
}
func allGesturesEnded() -> Bool {
let panEnded = isGestureEnded(gesture: panGestureRecognizer)
let pinchEnded = isGestureEnded(gesture: pinchGestureRecognizer)
let rotationEnded = isGestureEnded(gesture: rotateGestureRecognizer)
return panEnded && pinchEnded && rotationEnded
}
#objc func handlePan(_ gesture: UIPanGestureRecognizer) {
if gesture.state == .began {
gesturesAlreadyEnded = false
}
if !gesturesAlreadyEnded && isGestureEnded(gesture: gesture) {
canvasView.showHorizontalSnapIndicators(areVisible: false)
canvasView.showVerticalSnapIndicators(areVisible: false)
if (allGesturesEnded()) {
gesturesAlreadyEnded = true
print("Can create transformation command")
}
return
}
}
#objc func handlePinch(_ gesture: UIPinchGestureRecognizer) {
if gesture.state == .began {
gesturesAlreadyEnded = false
}
if !gesturesAlreadyEnded && isGestureEnded(gesture: gesture) {
if (allGesturesEnded()) {
gesturesAlreadyEnded = true
print("Can create transformation command")
}
return
}
}
#objc func handleRotation(_ gesture: UIRotationGestureRecognizer) {
if gesture.state == .began {
gesturesAlreadyEnded = false
}
if !gesturesAlreadyEnded && isGestureEnded(gesture: gesture) {
if (allGesturesEnded()) {
gesturesAlreadyEnded = true
print("Can create transformation command")
}
return
}
}
}
extension SCCanvasViewController: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer.view != otherGestureRecognizer.view {
return false
}
return true
}
}
Set it up to get multi-touch notifications, then examine the set of touches impacted by the event.
class TouchableView: UIView {
var touchViews = [UITouch:TouchSpotView]()
override init(frame: CGRect) {
super.init(frame: frame)
isMultipleTouchEnabled = true
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
isMultipleTouchEnabled = true
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
createViewForTouch(touch: touch)
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let view = viewForTouch(touch: touch)
// Move the view to the new location.
let newLocation = touch.location(in: self)
view?.center = newLocation
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
removeViewForTouch(touch: touch)
}
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
removeViewForTouch(touch: touch)
}
}
// Other methods. . .
}

Programmatically made buttons conflict with viewController’s gesture recognizers

Referring to my last question:
Spritekit: passing from UIButtons to buttons as SKSpriteNode
I’m working on a SpriteKit game: the user can tap and swipe on the screen to move sprites inside the scene and I added gesture recognizers in my ViewController for that.
Then I create a HUD to keep 4 buttons programmatically made with which the user could add other sprites to the scene.
I want my buttons fade and scale a little when pressed and then turn back to the original state, but it seems that they conflict with viewController’s gesture recognizers: buttons fade and scale down, but they stay in that state, don’t go back to normal state.
What can I do?
This is the Button class:
import SpriteKit
protocol ButtonDelegate: NSObjectProtocol {
func buttonClicked(sender: Button)
}
class Button: SKSpriteNode {
weak var delegate: ButtonDelegate!
var buttonTexture = SKTexture()
init(name: String) {
buttonTexture = SKTexture(imageNamed: name)
super.init(texture: buttonTexture, color: .clear, size: buttonTexture.size())
self.isUserInteractionEnabled = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var touchEndedCallback: (() -> Void)?
weak var currentTouch: UITouch?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if isUserInteractionEnabled {
setScale(0.9)
self.alpha = 0.5
if let currentTouch = touches.first {
let touchLocation = currentTouch.location(in: self)
for node in self.nodes(at: touchLocation) {
delegate?.buttonClicked(sender: self)
}
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
setScale(1.0)
self.alpha = 1.0
touchEndedCallback?()
print("tapped!")
}
}
This is the code I used in View Controller:
class ViewController: UIViewController, UIGestureRecognizerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let skView = view as! SKView
skView.isMultipleTouchEnabled = false
skView.presentScene(scene)
#IBAction func didTap(_ sender: UITapGestureRecognizer) {
game.myCode
}
#IBAction func didPan(_ sender: UIPanGestureRecognizer) {
let currentPoint = sender.translation(in: self.view)
if let originalPoint = panPointReference {
if abs(currentPoint.x - originalPoint.x) > (SquareSize * 0.9) {
if sender.velocity(in: self.view).x > CGFloat(0) {
//game.myCode
panPointReference = currentPoint
} else {
//game.myCode
panPointReference = currentPoint
}
}
} else if sender.state == .began {
panPointReference = currentPoint
}
}
#IBAction func didSwipe(_ sender: UISwipeGestureRecognizer) {
//game.myCode
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive shouldReceiveTouch: UITouch) -> Bool {
if UITouch .isKind(of: Button.self) {
return false
}
return true
}
func buttonClicked(sender: Button) {
//myCode
}
}
SOLVED IT!
Ok, it was simpler than I think.
(I took a break).
- I deleted UIgestures and added them programmatically;
- in my GameScene’s touchesEnded method I created a conditional to check which one of my node has been touched.
- in View Controller I add the gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:) method, to avoid conflicts with gesture recognizers attached to different view (my nodes are in two different views):
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer.view != otherGestureRecognizer.view {
return false
}
return true
}
I hope this will help…
I don't think they are interfering with the inbuilt gesture recognizers of the View Controller as you are not adding any gestures of your own, you are just overriding touchesBegin and touchesEnded.
Does "tapped" get printed out?
It is possible that touchesEnd() is not getting called, try implementing touchesCancelled() as well and see if that gets called.

Detect any tap outside the current view

Is there a way to detect any tap outside the current view? I unsuccessfully tried to implement something with the hitTest method but I am not sure to understand it well.
What you have to do is, In touchesBegan you have to get the first touch object from the touches set and you have to check the location of that touch in a view(inside which you want to detect the touch).
After you get the location of touch in View, you have to check whether your currentView(The view which you have to check whether tap was inside or outside).
If currentView's frame contains the touch location, that means touch has occurred inside the view otherwise outside.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
let touch = touches.first
guard let location = touch?.location(in: self.view) else { return }
if !currentView.frame.contains(location) {
print("Tapped outside the view")
} else {
print("Tapped inside the view")
}
}
Hope it Helps!
You can use UIGestureRecognizerDelegate protocol
extension YourViewController: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
shouldReceive touch: UITouch) -> Bool {
return (touch.view === self.view)
}
}
This will only returns "true" when the touch was on the background view but "false" if it was inside the your View.
Note : The identity operator === to compare touch.view with self.view. You want to know whether both variables refer to the same object.
And in viewDidLoad() you will create the gestureRecognizer and set delegate.
let gestureRecognizer = UITapGestureRecognizer(target: self,action: #selector(yourActionMethod))
gestureRecognizer.cancelsTouchesInView = false
gestureRecognizer.delegate = self
view.addGestureRecognizer(gestureRecognizer)

Function to detect if there's any touch occurring on the screen

I'm trying to create a simple function, similar to the touchesBegan, that detects if there's any touch occurring on the screen.
I've hit a brick wall trying it out myself because I'm not comfortable with UITouch class, but I really need some self made function, outside the touchesBegan default one.
I was trying to do something like this 'pseudo-code/swift'
func isTouchingTheScreen() -> Bool {
let someTouchHandleConstant: uitouch ???
if imTouchingTheScreen {
return true
} else {
return false
}
}
Do you have any hints?
PS: I know that code doesn't work, don't call that out, it was just to give you some 'image' of what I was trying to do (:
The idea
You can simply keep track of every touch begun, ended or cancelled by the user.
class GameScene: SKScene {
private var activeTouches = Set<UITouch>()
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
activeTouches.unionInPlace(touches)
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
activeTouches.subtractInPlace(touches)
}
override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
if let touches = touches {
activeTouches.subtractInPlace(touches)
}
}
var isTouchingTheScreen: Bool { return !activeTouches.isEmpty }
}
Keeping activeTouches updated
As you can see I am keeping updated the activeTouches Set.
Every time a touch does begin I add it to activeTouches. And every time a touch does end or is cancelled I remove it from activeTouches.
The isTouchingTheScreen computed variable
This allows me to define the isTouchingTheScreen computed property that simply returns true when the Set contains some element.
You can implement UITapGestureRecognizer as below:
var tapGesture :UITapGestureRecognizer!
override func didMoveToNode() {
// Add UITapGestureRecognizer to view
self.tapGesture = UITapGestureRecognizer(target: self, action: #selector(ViewController.touchedView(_:)))
view.addGestureRecognizer(tapGesture)
}
func touchedView(sender: UITapGestureRecognizer) {
print("view touched")
}
You could implement UITapGestureRecognizer:
// global var
var tapGesture :UITapGestureRecognizer!
override func didMoveToView() {
// Add tap gesture recognizer to view
self.tapGesture = UITapGestureRecognizer(target: self, action: #selector(GameScene.handleTap(_:)))
self.tapGesture.cancelsTouchesInView = false
view.addGestureRecognizer(tapGesture)
}
func handleTap(sender: UITapGestureRecognizer) {
print("GameScene tap")
if sender.state == .Ended {
var positionInScene: CGPoint = sender.locationInView(sender.view)
positionInScene = self.scene!.convertPointFromView(positionInScene)
let touchedNode = self.nodeAtPoint(positionInScene)
if touchedNode.name != "myHero" {
print("The SKSpriteNode myHero was tapped")
}
}
}
You can find more details in Apple docs here.

Disable touches everywhere except inside of a button? - Swift

When I die in my game, I want to ignore all touch events by the user EXCEPT if the user taps inside of or on the reset game button. Here is my code.
for touch in touches{
let location = touch.locationInNode(self)
if died == true{
Ball.physicsBody?.velocity = CGVectorMake(0, 0)
if resetGame.containsPoint(location){
restartScene()
runAction(SKAction.playSoundFileNamed("Woosh.wav", waitForCompletion: false))
}
else {
self.userInteractionEnabled = false
}
This is all inside of my touchesBegan. This was my attempt at ignoring the user's touch unless the location of the touch was within the size of button. How can I ignore a user's touches everywhere on the screen except the button? resetGame is an SKSpriteNode image.
There are two solutions to your issue.
The first case I want to propose to you is based to gesture recognizer.
You can separate the button from the other touches events and switch on/off the touches event by a boolean var like this:
Gesture recognizers:
In the global var declaration section of your class:
var tapGesture :UITapGestureRecognizer!
var enableTouches:Bool = true
override func didMoveToView(view: SKView) {
super.didMoveToView(view)
self.tapGesture = UITapGestureRecognizer(target: self, action: #selector(GameClass.simpleTap(_:)))
self.tapGesture.cancelsTouchesInView = false
view.addGestureRecognizer(tapGesture)
myBtn.name = "myBtn"
}
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
return self.enableTouches
}
func simpleTap(sender: UITapGestureRecognizer) {
print("simple tap")
if sender.state == .Ended {
var touchLocation: CGPoint = sender.locationInView(sender.view)
touchLocation = self.convertPointFromView(touchLocation)
let touchedNode = self.nodeAtPoint(touchLocation)
// do your stuff
if touchedNode.name == "myBtn" {
// button pressed, do your stuff
}
}
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
if died == true {
self.enableTouches = false // disable all touches but leave gestures enabled
//do whatever you want when hero is died
}
}
Only a Boolean
The second solution I want to propose is simply to stopping touches flow by using a simple boolean (it's not very elegant but it works).
This method look when button is tapped and the update method check if your hero is dead so all touches will be disabled:
In the global var declaration section of your class:
var enableTouches:Bool = true
override func didMoveToView(view: SKView) {
super.didMoveToView(view)
myBtn.name = "myBtn"
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
if (!enableTouches) {
return
}
let touch = touches.first
let positionInScene = touch!.locationInNode(self)
let touchedNode = self.nodeAtPoint(positionInScene)
// do your stuff
if touchedNode.name == "myBtn" {
// button pressed, do your stuff
if died == true {
self.enableTouches = false // disable all touches
//do whatever you want when hero is died
}
}
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
if (!enableTouches) {
return
}
let touch = touches.first
let positionInScene = touch!.locationInNode(self)
let touchedNode = self.nodeAtPoint(positionInScene)
// do your stuff
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
if (!enableTouches) {
return
}
}
The first case permit to you to have always the gesture enabled so you can do also other stuff with gestures when your hero will be died. The second case stop your touches when you press your button and do the "die flow". Choose which may be the most suitable for you.

Resources