I am trying to detect a tap gesture event on CALayers that are a sublayer of a UIScrollView. Here is how I've got the recognizer set up:
In viewDidLoad:
self.tapRecognizer = UITapGestureRecognizer(target: self, action: "tapHandler:")
self.tabScroller.addGestureRecognizer(tapRecognizer)
Tap handling method:
func tapHandler(recognizer: UITapGestureRecognizer) {
var touchLocation = recognizer.locationInView(recognizer.view)
if recognizer.state == UIGestureRecognizerState.Ended {
if let touchedLayer: CALayer = self.tabScroller.layer.hitTest(touchLocation) {
println("\(touchedLayer.name)")
}
}
}
I know that tapHandler is working because it will execute a basic println("") String placed anywhere inside of it.
But println("(touchedLayer.name)") continuously prints nil and I cannot figure out why it is not recognizing a tap above a CALayer.
The CALayers were added to tabScroller using
tabScroller.layer.addSublayer(brushB)
What am I doing wrong?
Your CALayer and UIView with the tap are on two different layers of the view hierarchy. Core Animation is lower level than view’s in fact, UIViews are made up of layers. Therefore, you have to use hitTest, or a like function to determine the layer which you are tapping.
Related
I'm trying to work out the best method for finding the exact subview (if any) at a given CGPoint in a UIView.
Part of my scene/ViewController is a custom UIView subclass (designed in a XIB), which presents a UIStackView of subviews (also custom, XIB-designed).
The VC has a UITapGestureRecognizer on that custom view, and in my #IBAction, I want to identify which specific one of its subviews was tapped, then handle it accordingly:
#IBAction func handleTap(recognizer:UITapGestureRecognizer) {
let tapLocation = recognizer.location(in: recognizer.view)
if let subviewTapped = customView.subviewAtLocation(tapLocation) {
handleTapForSubview(subviewTapped)
}
}
However, I don't know how to implement that subviewAtLocation(CGPoint) method. I wasn't able to find anything in the standard UIView methods.
Any advice on how it should be done would be welcome.
Alternatively, I considered adding a tap recognizer to each of the subviews instead, then delegating to the parent, then to the VC, but that feels inefficient, and like it's putting too much control logic in the views, rather than the VC.
Thank you.
A solution would be using the contains(point:) method of CGRect. The idea is to iterate over the stack view's subviews and check which subviews contains the touched point. Here:
#IBAction func handleTap(recognizer:UITapGestureRecognizer) {
let tapLocation = recognizer.location(in: recognizer.view)
let filteredSubviews = stackView.subviews.filter { subView -> Bool in
return subView.frame.contains(tapLocation)
}
guard let subviewTapped = filteredSubviews.first else {
// No subview touched
return
}
// process subviewTapped however you want
}
//use hitTest() method. this gives the view which contain the point
let subView = parentView.hitTest(point, with: nil)
Is there a way to change color of the uiview when pressed and released.
I tried it using touchesBegan and touchesEnded by overriding it but it effected the existing functionality. so without effecting the existing functionality can we achieve it ?
You can use UIGestureRecognizer to detect touch events on your view. Please refer below links for more info.
https://www.raywenderlich.com/433-uigesturerecognizer-tutorial-getting-started
https://developer.apple.com/documentation/uikit/uipangesturerecognizer
Example:
let myView = UIView()
let gesture = UITapGestureRecognizer(target: self, action: #selector(tapEventDetected(gesture:)))
myView.addGestureRecognizer(gesture)
#objc func tapEventDetected(gesture:UITapGestureRecognizer){
if gesture.state == .began{
//Set touch began color
}
if gesture.state = .ended{
//Set touch ended color
}
}
Thanks!
Does anyone know the best way how to implement this animation or library with similar functionality? I assume that it can be done via affine transforms. But maybe somebody knows some examples.
on tableView, you can use editActionForRowAt function(you can read more about it here https://developer.apple.com/documentation/uikit/uitableviewdelegate/1614956-tableview) but because you are using a collection view you have to do it by yourself.
Add every cell a UIPanGestureRecognizer and make the animation according to the to the pan gesture.
like this:
func setupSwipeGesture() {
swipeGesture = UIPanGestureRecognizer(target: self, action:#selector(swiped(_:)))
swipeGesture.delegate = self
self.addGestureRecognizer(swipeGesture)
}
func swiped(_ gestureRecognizer: UIPanGestureRecognizer) {
let xDistance:CGFloat = gestureRecognizer.translation(in: self).x
// do your animation
}
In my ViewController.swift, I have an array that contains custom UIViews. Every time one is created, I add a UIPanGestureRecognizer to it like this:
var panRecognizer = UIPanGestureRecognizer(target: self, action: "detectPan:")
newCard.gestureRecognizers = [panRecognizer]
This links to my detectPan(recognizer: UIPanGestureRecognizer) function, which handles movement. However, since I have multiple objects linked to the function, I'm not sure how I determine from which one the input is coming.
Is there anything like (the nonexistent) recognizer.target that I could use? Should I just handle the panning from within each of the custom UIViews instead?
Any help would be appreciated!
First of all, you should declare your panRecognizer with let.
let panRecognizer = UIPanGestureRecognizer(target: self, action: "detectPan:")
Second of all, you should not set the gestureRecognizers property of any UIView. This is bad practice because UIKit may have already added its own gesture recognizers to that view behind the scenes. If you subsequently remove those recognizers by assigning [panRecognizer] to that property, you may get unexpected behavior. To add your pan gesture recognizer, do this:
newCard.addGestureRecognizer(panRecognizer)
Then, in your detectPan(recognizer: UIPanGestureRecognizer) method you can detect which UIView was panned with the following code:
func detectPan(recognizer: UIPanGestureRecognizer) {
switch recognizer.view {
case self.customViewArray[0]:
// do something
case self.customViewArray[1]:
// do something else
case ... :
// ...
}
I set up a Swipe Gesture Recognizer and I connected it to the code, so that an UIImageView rotates when the user swipes to the left.
#IBAction func swipeToLeft(sender: AnyObject) {
UIView.animateWithDuration(1.0, animations: {
self.image.transform = CGAffineTransformRotate(self.image.transform, -3.14159265358979 )
})
}
I made sure the viewDidLoad method looked like this:
image.userInteractionEnabled = true
However, the UIImageView only gets transformed only once.
You can download the demo of the project from this link. Why is that happening?
I guess, the problem is that the moment you rotated your image, the gesture recognizer, associated with it, also got rotated. You can make sure yourself:
Make a swipe right-to-left. The image will rotate. Then make a swipe left-to-right. It will rotate again.
If you want to always handle a swipe right-to-left, you can do it in a couple of ways:
If your view is always rotated by 180 degrees, the easiest way is to change the gesture recognizer orientation (#LyndseyScott was faster than me to right the code for this one, you can check her answer :) ).
Another option (especially, if there might be a situation, when you rotate your view by some arbitrary angle), is to create a UIView on top of the view you want to rotate (but not as its subview!), and to add the gesture recognizer to it instead.
Just to elaborate on FreeNickname's answer, since the gesture recognizer "rotates" along with the UIImageView, you can change your code to the following so that the swipe gesture swaps directions along with the image and you can continue swiping from right to left to trigger the animation:
#IBAction func swipeToLeft(sender: UISwipeGestureRecognizer) {
UIView.animateWithDuration(1.0, animations: {
self.doubleDot.transform = CGAffineTransformRotate(self.doubleDot.transform, -3.14159265358979 )
}, completion: {
(value: Bool) in
if sender.direction == UISwipeGestureRecognizerDirection.Left {
sender.direction = UISwipeGestureRecognizerDirection.Right
} else {
sender.direction = UISwipeGestureRecognizerDirection.Left
}
})
}