UIPanGestureRecognizer is not working in iOS 13 - swift

We have developed an app in iOS 12 which worked really fine. Now in iOS 13 UIPanGestureRecognizer is not working any more.
I've search for solutions but didn't find anything.
#IBAction func handlePan(recognizer:UIPanGestureRecognizer) {
let translation = recognizer.translation(in: self.view)
if let view = recognizer.view {
let center = view.frame.origin.y + view.bounds.height / 2
if(center <= SliderView.bounds.height && center >= SliderView.bounds.minY) {
view.center = CGPoint(x:view.center.x, y:view.center.y + translation.y)
}
if(center > SliderView.bounds.height) {
view.center = CGPoint(x: view.center.x, y: view.center.y - 1)
}
if(center < SliderView.bounds.minY) {
view.center = CGPoint(x: view.center.x, y: view.center.y + 1)
}
lowerSliderView.frame = CGRect(x: 0, y: center, width: SliderView.bounds.width, height: SliderView.bounds.height - center)
slider = 1 - Float(center / SliderView.bounds.height)
slider = min(slider, 1.0)
slider = max(slider, 0.0)
}
recognizer.setTranslation(CGPoint.zero, in: self.view)
}
I expect that the slider will work on the app.

I had similar issues with my gesture recognizer on iOS13. (Worked fine on 12)
My problem was: I was setting .center = someValue, on my view which had the gesture recognizer on it, but that view also had Constraints on it. iOS13 doesn't seem to like it when you have constraints on a view, and are also setting it's frame manually. So i converted my code completely to just set the frame inside the gesture recognizer's handler method. iOS13 seems to have gotten more strict with preventing you from setting .center, or .frame manually if you also have constraints on that view and are calling layoutIfNeeded() causing a layout pass. I'm not sure about this, but for now, i'm back up and running using manual frame setting.
If your gesture recognizer event is not firing, please try to implement the following method and inspect the values inside to see if there are other gesture recognizers overlaid and competing for the touch gesture. Return TRUE for your gesture recognizer, and suppress others, or just return true for all of them. You need to set the delegate first.
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if (gestureRecognizer is UIPanGestureRecognizer || gestureRecognizer is UIRotationGestureRecognizer) {
print("Should recognize")
return true
} else {
print("Should NOT recognize")
return false
}
}
This is the setup of mine. works fine now, after I removed all the constraints from my view which had the recognizer on it. Constraints were "undoing" the translations which I had in the gesture recognizer method, only resulting in +1 or -1 movement of the view, and then it snapped back in place.
let recStart = UIPanGestureRecognizer(target: self, action: #selector(handleStartPan))
recStart.delegate = self
self.startView.addGestureRecognizer(recStart)
And handleStartPan:
#objc final func handleStartPan(_ gestureRecognizer: UIPanGestureRecognizer) {
if gestureRecognizer.state == .began || gestureRecognizer.state == .changed {
let translation = gestureRecognizer.translation(in: containerOfMyView)
// do stuff with translation.x or .y
...

Related

View blocked from moving after being rotated

I implemented touchesMoved method for moving my view around and RotationGestureRecognizer to rotate it. It works normal, I can move and rotate my view. The problem is that the view after being rotated cannot be moved anymore. It pins itself to center and doesn't go anywhere.
Here are the gifs as a visual description of the problem:
View is moved around its superview
View won't move after rotation
touchesMoved methode:
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let sv = superview, let touch = touches.first else { return }
let parentFrame = sv.bounds
let location = touch.location(in: self)
let previousLocation = touch.previousLocation(in: self)
var newFrame = self.frame.offsetBy(dx: location.x - previousLocation.x, dy: location.y - previousLocation.y)
newFrame.origin.x = max(newFrame.origin.x, 0.0)
newFrame.origin.x = min(newFrame.origin.x, parentFrame.size.width - newFrame.size.width)
newFrame.origin.y = max(newFrame.origin.y, 0.0)
newFrame.origin.y = min(newFrame.origin.y, parentFrame.size.height - newFrame.size.height)
self.frame = newFrame
}
RotationGestureRecognizer method:
#objc func rotationGestureHandler(recognizer:UIRotationGestureRecognizer) {
if let view = recognizer.view {
view.transform = view.transform.rotated(by: recognizer.rotation)
print(view.frame)
recognizer.rotation = 0
}
}
Any ideas why this can happen?
Thanks to everybody in advance
yourGestureRecognizer.cancelsTouchesInView = true (this is default value)
Because your Rotate Gesture "cancel" your touch in this view, so the touchMoved isn't triggered.
When this property is true (the default) and the gesture recognizer recognizes its gesture, the touches of that gesture that are pending aren’t delivered to the view and previously delivered touches are canceled through a touchesCancelled(_:with:) message sent to the view. If a gesture recognizer doesn’t recognize its gesture or if the value of this property is false, the view receives all touches in the multi-touch sequence.
Check this here: https://developer.apple.com/documentation/uikit/uigesturerecognizer/1624218-cancelstouchesinview

How do I zoom the view when the player zooms in or out?

So I just learned how to make a view zoom in and out, and I did it for my gamescene but theres just a black box right outside the view, like this
enter image description here
Here's my code that I used to allow zooming in and out, this is in my didMove function
let pinch = UIPinchGestureRecognizer(target: self, action: #selector(handlePinch(sender:)))
view.addGestureRecognizer(pinch)
and this is inside no function, just the GameScene class
#objc func handlePinch(sender: UIPinchGestureRecognizer) {
guard sender.view != nil else { return }
if sender.state == .began || sender.state == .changed {
sender.view?.transform = (sender.view?.transform.scaledBy(x: sender.scale, y: sender.scale))!
sender.scale = 1.0
}
}
How would I change the view whenever someone zooms in or out?

Simultaneous gestures and scaling of pinch Gesture in swift

Hopeing for a bit of enlightenment. I have a piece of code. Which is working fairly OK.
I have 2 gestures implemented - pinch and rotate.
let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(pinchAction(sender:)))
view.addGestureRecognizer(pinchGesture)
pinchGesture.delegate = self
let rotateGesture = UIRotationGestureRecognizer(target: self, action: #selector(rotateAction(sender:)))
view.addGestureRecognizer(rotateGesture)
rotateGesture.delegate = self
and then this function which I found here on another thread.
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if (gestureRecognizer is UIPanGestureRecognizer || gestureRecognizer is UIRotationGestureRecognizer) {
return true
} else {
return false
}
}
my class is set to
class GameScene: SKScene, UIGestureRecognizerDelegate {
so my first question is. How the heck is this function actually being called? Is it automatically called because it's called gestureRecognizer? I don't call it elsewhere as a function in my code. Sorry just find it confusing.
the other interesting issue is that as I rescale my SKSprite node with the pinch function I want it to not jump back to the scale of the initial pinch. I have set a variable for the scale of the node being adjusted and take that in the pinch began and also adjust the variable when pinch ends as so:
#objc func pinchAction(sender:UIPinchGestureRecognizer){
if sender.state == .began{
//print("Pinch Began")
pointer.setScale(CGFloat(protractorScale * 0.5))
protractor.setScale(CGFloat(protractorScale * 0.45))
}
if sender.state == .changed{
// print("Pinch Change")
let newScale = sender.scale
protractorScale = Double(newScale)
print(newScale)
pointer.setScale(CGFloat(protractorScale * 0.5))
protractor.setScale(CGFloat(protractorScale * 0.45))
ballSpeed = Float(newScale * 500)
}
if sender.state == .ended{
// print("Pinch Ended")
let newScale = sender.scale
protractorScale = Double(newScale)
}
}
but it still jumps to whatever pinch position I start with while it ought to stay stable between individual pinches rather than reseting.
I appreciate my code is simplistic so am also open to suggestions for a tighter code. This is my first attempt at using simultaneous gestures.Thanks
I finally understood that obviously the pinch gesture always initially returns a pinch scale factor of 1. Obviously! Once I realised that and after KnightOfDragon's advice it was straightforward.
#objc func pinchAction(sender:UIPinchGestureRecognizer){
if sender.state == .began{
let startScale = sender.scale
print("initial pinch scale \(startScale)")
pointer.setScale(CGFloat(protractorScale))
protractor.setScale(CGFloat(protractorScale))
}
if sender.state == .changed{
let getScale = sender.scale
print(getScale)
let newScale = (CGFloat(protractorScale) * getScale)
pointer.setScale(newScale)
protractor.setScale(newScale)
ballSpeed = Float(newScale * 100)
ballSpeedIndicator.text = "\(Int(ballSpeed))"
}
if sender.state == .ended{
// print("Pinch Ended")
let newScale = sender.scale
protractorScale = protractorScale * Double(newScale)
}
}
I set my initial node scale also to 1 as a global variable. So now in the state ended I return that * the pinch scale and the scaling now remains perfectly stable.

Conflict between Pan Gesture and Tap Gestures

I'm currently working on a game that uses UIGestureRecognizers. I'm using the pan gesture to move the player around and the tap gesture to detect other UI button touches. Everything seems to work fine except that there is a conflict between the 2 gestures. Whenever the player is on the move (pan gesture gets recognized) the game ignores all my tap gestures (Once the pan gesture is recognized, the view won't recognize tap gestures).
Can someone please show me how to make the 2 gestures work together. I want the player to stop moving when a UI button gets tapped. In another word, I want to cancel the pan gesture whenever a tap gesture is recognized.
Thank you so much in advance!
Here is how I setup the 2 gestures:
let singleTap = UITapGestureRecognizer(target: self, action: #selector(doSingleTap))
singleTap.numberOfTapsRequired = 1
singleTap.delegate = self
self.view?.addGestureRecognizer(singleTap)
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePan))
panGesture.minimumNumberOfTouches = 1
panGesture.delegate = self
self.view?.addGestureRecognizer(panGesture)
#objc func handlePan(gestureReconizer: UIPanGestureRecognizer) {
if isPaused || player.isInAction {return}
let translation = gestureReconizer.translation(in: self.view)
if gestureReconizer.state == .changed {
let angle = 180 + (atan2(-translation.x, translation.y) * (180/3.14159))
player.movementAngle = angle
player.atState = .Walk
}
if gestureReconizer.state == .ended {
player.movementAngle = 0.0
if player.atState == .Walk {
player.atState = .Idle
}
}
}
#objc func doSingleTap(gestureReconizer: UITapGestureRecognizer) {
let originaTapLocation = gestureReconizer.location(in: self.view)
let location = convertPoint(fromView: originaTapLocation)
let node = atPoint(location)
switch node.name {
case "HeroAvatar":
//do stuff here
break
case "Fire":
//do stuff here
break
case "Potion":
//do stuff here
break
default:
break
}
}
You need to implement delegate method of UIGestureRecognizerDelegate like below:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
// Don't recognize a single tap gesture until a pan gesture fails.
if gestureRecognizer == singleTap &&
otherGestureRecognizer == panGesture {
return true
}
return false
}
Hope this will work for you :)
For more info Apple Doc Preferring One Gesture Over Another

What is the best way to zoom and deplace nodes?

I'm working with Swift 3, SpriteKit and Xcode.
So I have a node named backgroundNode, and I attach every nodes of my game to this backgroundNode.
Now I would like to be able to zoom on my game with a pinch gesture, and when I'm zoomed in to navigate in my game.
I see 2 possibilities to do this :
deplace the background node and change its scale to zoom in and zoom out,
use SKCameraNode
What do you think is the best option ?
I already tried the first option but the zoom gesture is quite complex as if I scale the backgroundNode up when I want to zoom, the anchor point is in 0;0 and not 0.5;0.5 so it doesnt zoom where the pinch gesture is detected, but from the bottom right corner, I don't know if you see what I mean.
And for the second option, I can't manage to move the camera without having a glitchy effect, maybe my code is wrong but it really seems correct.
Can you help me ?
Edit : So I got it working using SKCameraNode and UIPanGestureRecognizer, here is the code :
var cam: SKCameraNode!
let panGesture = UIPanGestureRecognizer()
override func didMove(to view: SKView)
{
cam = SKCameraNode()
camera = cam
cam.position = CGPoint(x: playableRect.midWidth, y: playableRect.midHeight)
addChild(cam)
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(GameScene.panFunction))
view.addGestureRecognizer(panGesture)
}
func panFunction(pan : UIPanGestureRecognizer)
{
let deltaX = pan.translation(in: view).x
let deltaY = pan.translation(in: view).y
cam.position.x -= deltaX
cam.position.y += deltaY
pan.setTranslation(CGPoint(x: 0, y: 0), in: view)
}
Now I'm struggling with the Zoom. I tried using UIPinchGestureRecognizer but it doesn't work as good as the pan gesture, here is what I tried :
var firstPinch: CGFloat = 0
var pinchGesture = UIPinchGestureRecognizer()
let panGesture = UIPanGestureRecognizer()
var cam: SKCameraNode!
override func didMove(to view: SKView)
{
let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(GameScene.pinchFunction))
view.addGestureRecognizer(pinchGesture)
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(GameScene.panFunction))
view.addGestureRecognizer(panGesture)
}
func pinchFunction(pinch : UIPinchGestureRecognizer)
{
if UIGestureRecognizerState.began == pinch.state
{
firstPinch = pinch.scale
}
actualPinch = pinch.scale
cam.xScale -= actualPinch - firstPinch
cam.yScale -= actualPinch - firstPinch
}
How would you do it ?
You need to post your code. I helped someone with this in another forum. Their code + my answer should give a general idea of what to do:
https://forums.developer.apple.com/message/192823#192823
Basically it involves UIPanGestureRecognizer etc, and then a bit of delta-scale logic to adjust for the new bounds of the camera.