3D Touch force gesture activates cell tap on touch end - ios

I have subclassed a UITableView in my app so that I can intercept touch events. I am using this to allow me to provide 3D Touch gestures on the entire view (including on top of the table view).
This works great, however the problem is that using 3D Touch on one of the cells and then releasing your finger activates the cell tap.
I need to only activate the cell tap if there is no force exerted. I should explain that I am fading an image in gradually over the entire screen as you apply pressure.
Here is my subclass:
protocol PassTouchesTableViewDelegate {
func touchMoved(touches: Set<UITouch>)
func touchEnded(touches: Set<UITouch>)
}
class PassTouchesTableView: UITableView {
var delegatePass: PassTouchesTableViewDelegate?
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
super.touchesMoved(touches, withEvent: event)
self.delegatePass?.touchMoved(touches)
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
super.touchesEnded(touches, withEvent: event)
self.delegatePass?.touchEnded(touches)
}
}
And here are the methods I'm calling from my view controller when the touches end and move:
internal func touchMoved(touches: Set<UITouch>) {
let touch = touches.first
self.pressureImageView.alpha = (touch!.force / 2) - 1.0
}
internal func touchEnded(touches: Set<UITouch>) {
UIView.animateWithDuration(0.2, animations: {
self.pressureImageView.alpha = 0.0
})
}

You could create a boolean named isForceTouch which is set to false in touchesBegan, and then once force touch is detected, set it to true. Then in didSelectRowAtIndexPath just return false if isForceTouch is true. It may need tweaking but that should work.

Related

Get touch type in hitTest() or pointInside()

I'm implementing a passThroughView by creating a transparent View on top and override hitTest().
passThroughView should consume touches from Apple Pencil and if touch type is not from pencil, it pass touches to the view underneath.
The problems are:
parameter "event" in hitTest contains no touch, so I can't check touch type in hitTest
I can get touch from touchesBegan and check touch type, but it get called only after hitTest returned true
I subclass UIWindow and override sendEvent() but this function also called after hitTest (and I don't know why)
class WindowAbleToKnowTouchTypes: UIWindow {
override func sendEvent(_ event: UIEvent) {
if event.type == .touches {
// This get called after Hittest
if event.allTouches!.first!.type == .pencil {
print("This touch is from Apple Pencil")
}
}
super.sendEvent(event)
}
}
Is there anyway to check touchType to decide to pass or consume touches?
I ended up using a different approach, it could be useful for many cases: If I can't get touchType in hitTest(), I can still get the touchType with GestureRecognize:
class CustomGestureRecognizer : ImmediatePanGesture {
var beganTouch : UITouch!
var movedTouch : UITouch!
var endedTouch : UITouch!
override func shouldReceive(_ event: UIEvent) -> Bool {
// You can check for touchType here and decide if this gesture should receice the touch or not
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
// Save touch for later use
if let firstTouch = touches.first {
beganTouch = firstTouch
}
super.touchesBegan(touches, with: event)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
// Save touch for later use
if let touch = touches.first {
movedTouch = touch
}
super.touchesMoved(touches, with: event)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
// Save touch for later use
if let touch = touches.first {
endedTouch = touch
}
super.touchesEnded(touches, with: event)
}
}
In the target function of the gestureRecognizer, you can get the UITouch by:
let beganTouch = customGesture.beganTouch
let touchType = beganTouch.touchType

3D Touch on UITextView

Is it possible to implement 3D Touch with UITextView?
I tried this example. And this method hadn't called by system when I tapped on any view on the screen. As I read there is no way to use 3D touch with UITapGestureRecognizer. So is there any ways to do this?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("test0")
if let touch = touches.first {
print("test1")
if traitCollection.forceTouchCapability == UIForceTouchCapability.available {
print("test2")
print("FORCE TOUCH VALUE \(touch.force)")
}
}
}

Tap gesture to NOT occur when touching a specific location - SpriteKit

I have used touchesBegan to provide functionality for my UIButtons and have used a tapped gesture to provide functionality for my main player SKSpriteNode making it jump when triggered.
//Code regarding the UIButton touch
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
//touches began is only used for GUI buttons -> not to affect player
for touch: AnyObject in touches {
//We get location of the touch
let locationOfTouch = touch.location(in: self)
if muteButton.contains(locationOfTouch) { //mute the game
timer.invalidate()
audioPlayer.volume = 0
}
//Code regarding the tap
let tap = UITapGestureRecognizer(target: self, action: #selector(GameScene.tapped(gesture:)))
tap.cancelsTouchesInView = false
self.view!.addGestureRecognizer(tap)
......
func tapped(gesture: UIGestureRecognizer) { //used to make the player jump
player.physicsBody!.applyImpulse(CGVector(dx: 0, dy: 60))
player.physicsBody!.affectedByGravity = true */
}
My problem is that when I press on the restartButton the tap gesture is also activated later when the touch ends. Is there anything I can do?
The main issue is that the two separate systems for detecting touches (using gesture recognizers and using the touchesBegan/Moved/Ended methods) are in conflict.
One solution is to enable and disable the gesture recognizer if the touch is inside one of the buttons.
In the touchesBegan method, if the touch is inside a button, disable the tap gesture recognizer:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches {
let locationOfTouch = touch.location(in: self)
if muteButton.contains(locationOfTouch) {
// mute action
tap.isEnabled = false
}
}
}
Then in touchesEnded and touchesCancelled, re-enable the gesture recognizer:
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
tap.isEnabled = true
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
tap.isEnabled = true
}
This way, if the touch is inside a button, the tap gesture recognizer will not fire. Whenever any touch is complete, we always re-enable the gesture recognizer in case the next touch is meant to make the player jump.
I have tested this out in an empty project, and it works.
Hopefully that helps! Good luck with your game.

how i can differentiate between a gesture and a touch in spriteKit and swift?

i have two functions, one is activated when i touch the scene and another one when i make a gesture down, but when i make a gesture the scene detect a touch and execute both functions
class GameScene: SKScene, SKPhysicsContactDelegate {
override func didMoveToView(view: SKView) {
/* Setup your scene here */
// physics & collisions
physicsWorld.gravity = CGVectorMake(0.0, 0.0)
physicsWorld.contactDelegate = self
// Swipe down
let swipeDown:UISwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: Selector("swipedDown:"))
swipeDown.direction = .Down
view.addGestureRecognizer(swipeDown)
}
//MARK: Swipes
func swipedDown(sender:UISwipeGestureRecognizer){
//first function
swipeDown()
}
//MARK: Touches
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
for touch in touches {
let location = touch.locationInNode(self)
// second function
attack()
}
}
}
Instead of touchesBegan, try using a tap gesture (UITapGestureRecognizer) for attack action.
This should not be necessary but if you still get conflict between swipe and tap, make the tap gesture depend on swipe's failure using requireGestureRecognizerToFail to ensure that a tap is not detected during a swipe.
You could try something like this
var swiped = Bool()
func swipedDown(sender:UISwipeGestureRecognizer){
//first function
swipeDown()
swiped = true
}
Override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
for touch in touches {
let location = touch.locationInNode(self)
// second function
if swiped == false {
attack()
}
}
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
swiped = false
}
}
Hope this helps.

Scroll SpriteNode using touches

I want to scroll a node in sprite kit without uiscrollview
// In touchesMoved
let touch: UITouch = touches.first as! UITouch;
let location:CGPoint = touch.locationInNode(self);
let lastPosition = touch.previousLocationInNode(self)
var newLoc = selectNode.position.y + (location.y - lastPosition.y)
// Check if the top part is reached
if(newLoc < selectNodeStart){
newLoc = selectNodeStart
}
if(selectNode.position.y >= selectNodeStart){
// let sk = SKAction.moveToY(newLoc, duration: 0.1)
// selectNode.runAction(sk)
selectNode.position.y = newLoc
}
If I use the sk action the result is very horrible, but without the result is also horrible
An Elegant way of doing this in SpriteKit is using a UIPanGestureRecognizer to detect the touch, and inside of that you will create a sequence of SKActions that will accelerate or decelerate the movement. You might have to use the update method and/or touches to handle the pan stopping or another immediately starting. Unfortunately, if you want to use only SpriteKit you will have to use SKActions to implement this.
I thought I'd also mention an easier (and possible better looking) way to do this. Take a look at "ScrollKit", which works pretty well (although it does use a UIScrollView): https://github.com/bobmoff/ScrollKit
If you found that the UIScrollView doesn't pass touches up to touchesMoved, you could try a few different things.
- You can add a UITapGestureRecognizer and set on the UIScrollView CanCancelContentTouches to YES.
- You can subclass UIScrollView and override the touchesMethods so that if the user isn't scrolling, touch information will be passed up the responder chain:
override func touchesBegan(touches: NSSet, withEvent event: UIEvent){
if (!self.dragging){
self.nextResponder.touchesBegan(touches , withEvent: event)
}
else{
super.touchesBegan(touches , withEvent: event)
}
}
override func touchesMoved(touches: NSSet, withEvent event: UIEvent){
if (!self.dragging){
self.nextResponder.touchesMoved(touches , withEvent:event)
}
else{
super.touchesMoved(touches , withEvent: event)
}
}
override func touchesEnded(touches: NSSet, withEvent: event UIEvent){
if (!self.dragging){
self.nextResponder.touchesEnded(touches , withEvent: event)
}
else{
super.touchesEnded(touches , withEvent: event)
}
}

Resources