Each tap adds an object on my scene, but I want to add it only once and then disable the Tap gesture. I looked everywhere but none of them are working with my code. Can someone help me with this? Either limiting the tap to 1 only or disabling it. I tried adding the tap gesture as Outlet and then setting .isEnabled = false but it is still not working.
class ARScene: UIViewController, ARSCNViewDelegate, UIGestureRecognizerDelegate {
#IBOutlet weak var sceneView: ARSCNView!
var tap : UITapGestureRecognizer!
override func viewDidLoad() {
super.viewDidLoad()
self.sceneView.debugOptions = [ARSCNDebugOptions.showFeaturePoints, ARSCNDebugOptions.showWorldOrigin]
let configuration = ARWorldTrackingConfiguration()
self.sceneView.session.run(configuration)
// Set the view's delegate
sceneView.delegate = self
sceneView.debugOptions = [ARSCNDebugOptions.showFeaturePoints]
// Show statistics such as fps and timing information
sceneView.showsStatistics = true
// Create a new scene
// let scene = SCNScene(named: "art.scnassets/ship.scn")!
// Set the scene to the view
//sceneView.scene = scene
registerGestureRecognizer()
}
func registerGestureRecognizer(){
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap))
tap.numberOfTapsRequired = 1
sceneView.addGestureRecognizer(tap)
}
#objc func handleTap(gestureRecognizer: UIGestureRecognizer){
//let touchLocation = gestureRecognizer.location(in: sceneView)
let sceneLocation = gestureRecognizer.view as! ARSCNView
let touchLocation = gestureRecognizer.location(in: sceneLocation)
let hitResult = self.sceneView.hitTest(touchLocation, types: [.existingPlaneUsingExtent, .estimatedHorizontalPlane])
if hitResult.count > 0 {
guard let hitTestResult = hitResult.first else{
return
}
let node = SCNNode()
let scene = SCNScene(named: "art.scnassets/bucket/Bucket2.scn")
let nodeArray = scene!.rootNode.childNodes
for childNode in nodeArray{
node.addChildNode(childNode as SCNNode)
}
let worldPos = hitTestResult.worldTransform
node.scale = SCNVector3Make(0.009,0.009,0.009);
node.position = SCNVector3(x: 0, y: worldPos.columns.3.y, z: -1.4)
sceneView.scene.rootNode.addChildNode(node)
tap.isEnabled = false //NOT WORKING, I want to stop tap gesture here
}
}
In order to disable your tapGesture you need to have reference to the same gesture instance you assign tap to.
There are two ways either create global instance so you can change its properties like Enable/Disable from anywhere
Or access the variable from the action/method for that gesture.
Create the global varibale for your tap
like
var tap: UITapGestureRecognizer! // global varibale
func registerGestureRecognizer(){
tap = UITapGestureRecognizer(target: self, action: #selector(handlePanGesture(_:)))
tap.numberOfTapsRequired = 1
sceneView.addGestureRecognizer(tap)
}
and then disable this in taphandle
tap.isEnabled = false // disable the gesture
Or
2 Update your handle methods to
#objc func handleTap(withTapRecognizer tapGesture: UITapGestureRecognizer) {
........
tapGesture.isEnabled = false // use the instance variable and disable the gesture this will disable the gesture from which its been called
}
For you to access the gesture in the function, you need to initialize it using
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap(gestureRecognizer:)))
Then in your handleTap method, you can this at the end of the function
gestureRecognizer.view?.removeGestureRecognizer(gestureRecognizer)
Related
I have a UICollectionView that is basically a chat log. I have an imageView in some of the cells and added the ability to expand an image to full screen on tap.
///
ChatLogMessageCell.swift
/**
*
* I add the target to the UIButton with an image as a background
*/
messageImage.addTarget(self, action: #selector(fullscreenImage), for: .touchUpInside)
/*
* Full screen code
*/
#objc func fullscreenImage() {
if let chatlog = parentViewController as? ChatLogController {
let imageScroll = UIScrollView()
imageScroll.delegate = self
imageScroll.minimumZoomScale = 1.0
imageScroll.maximumZoomScale = 5.0
imageScroll.frame = UIScreen.main.bounds
let newImageView = UIImageView(image: messageImage.backgroundImage(for: .normal))
newImageView.frame = UIScreen.main.bounds
newImageView.backgroundColor = .black
newImageView.contentMode = .scaleAspectFit
newImageView.isUserInteractionEnabled = true
imageScroll.addSubview(newImageView)
chatlog.view.addSubview(imageScroll)
chatlog.navigationController?.isNavigationBarHidden = true
chatlog.tabBarController?.tabBar.isHidden = true
let tap = UITapGestureRecognizer(target: self, action: #selector(dismissFullscreenImage))
newImageView.addGestureRecognizer(tap)
}
}
#objc func dismissFullscreenImage(_ sender: UITapGestureRecognizer) {
if let chatlog = parentViewController as? ChatLogController {
chatlog.navigationController?.isNavigationBarHidden = false
chatlog.tabBarController?.tabBar.isHidden = false
sender.view?.removeFromSuperview()
}
}
When The fullscreen image is removed the ChatLogController is no longer interactable. I can't scroll or re-enter fullscreen mode on an image.What am I missing here? I simply want to dismiss the full screen image and allow the user to choose another image or just scroll through the messages.
Here you remove the imageView
sender.view?.removeFromSuperview()
while you need to remove the scrollView like
sender.view?.superview?.removeFromSuperview()
I have function which creates a drag line to connect 2 buttons to each other. This works fine but if some buttons overlap each other, it will select both if I drag over where they overlap. I only want to connect the top button.
I think the issue is with the sender.location selecting layers on top and below. Is there a way to tell the sender.location to only select the top view? Thanks for any input and direction
func addPanReconiser(view: UIView){
let pan = UIPanGestureRecognizer(target: self, action: #selector(DesignViewController.panGestureCalled(_:)))
view.addGestureRecognizer(pan)
}
#objc func panGestureCalled(_ sender: UIPanGestureRecognizer) {
let currentPanPoint = sender.location(in: self.view)
switch sender.state {
case .began:
panGestureStartPoint = currentPanPoint
self.view.layer.addSublayer(lineShape)
case .changed:
let linePath = UIBezierPath()
linePath.move(to: panGestureStartPoint)
linePath.addLine(to: currentPanPoint)
lineShape.path = linePath.cgPath
lineShape.path = CGPath.barbell(from: panGestureStartPoint, to: currentPanPoint, barThickness: 2.0, bellRadius: 6.0)
for button in buttonArray {
let point = sender.location(in: button)
if button.layer.contains(point) {
button.layer.borderWidth = 4
button.layer.borderColor = UIColor.blue.cgColor
} else {
button.layer.borderWidth = 0
button.layer.borderColor = UIColor.clear.cgColor
}
}
case .ended:
for button in buttonArray {
let point = sender.location(in: button)
if button.layer.contains(point){
//DO my Action here
lineShape.path = nil
lineShape.removeFromSuperlayer()
}
}
default: break
}
}
}
Note: some of the lines of codes are from custom extensions. I kept them in as they were self explanatory.
Thanks for the help
There is a way to walk around. It seems like you simply want your gesture end up at one button above all the others, thus by adding a var outside the loop and each time a button picked, comparing with the var of its level at z.
case .ended:
var pickedButton: UIButton?
for button in buttonArray {
let point = sender.location(in: button)
if button.layer.contains(point){
if pickedButton == nil {
pickedButton = button
} else {
if let parent = button.superView, parent.subviews.firstIndex(of: button) > parent.subviews.firstIndex(of: pickedButton!) {
pickedButton = button
}
}
}
}
//DO my Action with pickedButton here
lineShape.path = nil
lineShape.removeFromSuperlayer()
A UIView has a property called subViews where elements with higher indexes are in front of the ones with lower indexes. For instance, subView at index 1 is in front of subView with index 0.
That being said, to get the button that's on top, you should sort your buttonArray the same way subViews property of UIView is organized. Assuming that your buttons are all siblings of the same UIView (this might not be necessarily the case, but you can tweak them so you get them sorted correctly):
var buttonArray = view.subviews.compactMap { $0 as? UIButton }
Thus, keeping your buttonArray sorted that way, the button you want is the one that contains let point = sender.location(in: button) with higher index in the array.
I have a SpriteKit scene that can have thousands of distinct nodes in it. I am also implementing a single-tap gesture recognizer on the scene, in the hopes that I can determine which node has been touched in the scene once the gesture recognizer is triggered. Currently, my (non-working) code looks like this:
#objc func singleTap(_ sender: UIPinchGestureRecognizer) {
print("single tap gesture recognized")
if sender.numberOfTouches == 1 {
let touchPoint = sender.location(in: self.view)
let touchedNode = self.atPoint(touchPoint)
if let name = touchedNode.name
{
if name == "newMapButton"
{
print("newMapButton Touched")
} else {
print("what did you touch?")
}
}
}
}
The gesture recognizer is working. When I touch the new map button I get the "single tap gesture recognized" in the console, but nothing more. What am I doing wrong here?
In GameScene file, I created my button in didMove method like so
let btnTest = SKSpriteNode(imageNamed: "button")
btnTest.setScale(0.2)
btnTest.name = "Button"
btnTest.zPosition = 10
btnTest.position = CGPoint(x: 100, y: 200)
self.addChild(btnTest)
Adding Gesture in didMove:
let tapRec = UITapGestureRecognizer()
tapRec.addTarget(self, action:#selector(GameScene.tappedView(_:) ))
tapRec.numberOfTouchesRequired = 1
tapRec.numberOfTapsRequired = 1
self.view!.addGestureRecognizer(tapRec)
Finally implementing tappedView method
#objc func tappedView(_ sender:UITapGestureRecognizer) {
if sender.state == .ended {
var post = sender.location(in: sender.view)
post = self.convertPoint(fromView: post)
let touchNode = self.atPoint(post)
if let name = touchNode.name
{
if name == "Button"
{
print("newMapButton Touched")
} else {
print("what did you touch?")
}
}
}
}
I have a Google-Map + marker. I know how to make a marker draggable. The standard behaviour is to 'Long-Press' a marker and you can drag it.
What I want is to drag the marker by swiping over the map. It shouldn't be neccessary to hit the marker. User swipes over the map from left to right and simultanously the marker changes position from left to right where the distance equals swipe-length.
I can't find a suitable solution in the GM-API. Any ideas?
I'm using Swift 2.2
var marker: GMSMarker!
func createMarker(title: String, target: CLLocationCoordinate2D) {
marker = GMSMarker(position: target)
marker.appearAnimation = kGMSMarkerAnimationPop
marker.map = map
}
func activateDragMode() {
marker.draggable = true
map.settings.scrollGestures = false
map.settings.zoomGestures = false
map.settings.rotateGestures = false
}
The GoogleMap-API doesn't provide the method I need. But i found a solution:
map.settings.consumesGesturesInView = false
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(panRecognition))
view.addGestureRecognizer(panGestureRecognizer)
func panRecognition(recognizer: UIPanGestureRecognizer) {
if marker.draggable {
let markerPosition = map.projection.pointForCoordinate(marker.position)
let translation = recognizer.translationInView(view)
recognizer.setTranslation(CGPointZero, inView: view)
let newPosition = CGPointMake(markerPosition.x + translation.x, markerPosition.y + translation.y)
marker.position = map.projection.coordinateForPoint(newPosition)
}
}
I had to deactivate 'consumesGesturesInView' to add my own PanGestureRecognizer, which manipulates the marker.
Swift 5.1
By analysis on google api and other method, I did not get the proper one. The best and sweet answer for this question is by using pan gesture.
add the pan gesture to the mapView as
self.mapView.settings.consumesGesturesInView = false
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(self. panHandler(_:)))
self.mapView.addGestureRecognizer(panGesture)
Implementation of pan gesture method as
#objc private func panHandler(_ pan : UIPanGestureRecognizer){
if pan.state == .ended{
let mapSize = self.mapView.frame.size
let point = CGPoint(x: mapSize.width/2, y: mapSize.height/2)
let newCoordinate = self.mapView.projection.coordinate(for: point)
print(newCoordinate)
//do task here
}
}
This is not Sprite Kit.
If I have a variable like the one below
var value = 0
How am I able to increase the value if the user drags right and decrease if they drag left?
Thanks!
Like Caleb commented, Ray's tutorial is great, but if you want the actual swift example, please check the next example:
class ViewController: UIViewController, UIGestureRecognizerDelegate {
private var value: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.blackColor()
let recognizer = UIPanGestureRecognizer(target: self, action: Selector("handleDragging:"))
let inputView = UIView(frame: CGRectMake(0, 0, 100, 100))
inputView.backgroundColor = UIColor.whiteColor()
inputView.userInteractionEnabled = true
inputView.addGestureRecognizer(recognizer)
self.view.addSubview(inputView)
}
func handleDragging(recognizer: UIPanGestureRecognizer) {
if (recognizer.state == .Changed) {
let point = recognizer.velocityInView(recognizer.view?.superview)
if (point.x > 0) {
self.value++;
} else {
self.value--;
}
println(self.value)
}
}
}
You can use the velocityInView method of UIPanGestureRecognizer to determine which direction you're going. It returns a CGPoint, so you can pull out the x and y values as you wish. Positive is right/down, negative is left/up.