How to identify which subView has been pressed in Swift? - ios

I have a built a UIView class called SetView. In its initializer I create multiple subviews and later in ViewController I want to determine which subView has been pressed. in my viewDidLoad method I iterate through all the subviews add them to a class array of UIView called mySubViews and it my getIndex method, I am trying to retrieve the value which is always retrieved as nil. I suppose that it is my main view that is passed as a sender rather than particular subviews but I don't know how to pass specific subviews since #selector does not accept argument. I would appreciate any suggestions on how I could determine which subview was pressed to update features of a given subview.
override func viewDidLoad() {
super.viewDidLoad()
for view in setView.subviews {
mySubViews.append(view)
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(getIndex(_:)))
gestureRecognizer.delegate = self
view.addGestureRecognizer(gestureRecognizer)
}
}
#objc func getIndex(_ sender:UIView) {
print(mySubViews.index(of: sender))
}

The sender should be the gesture recognizer. Then give your views a tag and set the same tag for your gesture recognizer. Then you can get the view with viewWithTag.
Or with your array it could be like
override func viewDidLoad() {
super.viewDidLoad()
var index = 0
for view in setView.subviews {
mySubViews.append(view)
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(getIndex(_:)))
gestureRecognizer.tag = index
gestureRecognizer.delegate = self
view.addGestureRecognizer(gestureRecognizer)
index += 1
}
}
#objc func getIndex(_ sender: UITapGestureRecognizer) {
print(mySubViews[sender.tag])
}

The sender in your target method is a gesture recognizer. The fact that it is only interpreted as UIView will always return nil in your call.
Try the following:
#objc func getIndex(_ sender: UIGestureRecognizer) {
print(mySubViews.index(of: sender.view))
}
Still I would prefer you would use a single gesture recognizer on the super view. Then you can check the hit view by checking if the gesture recognizer was within the view bounds:
#objc func getIndex(_ sender: UIGestureRecognizer) {
let allViewsAtGestureLocation = mySubViews.filter { $0.bounds.contains(sender.location(in: $0)) }
let firstHitView = mySubViews.first(where: { $0.bounds.contains(sender.location(in: $0)) })
}
I assume you would need the second one. From it you can again find an index.

Maybe try something like this:
for view in setView.subviews {
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(getIndex(_:)))
gestureRecognizer.delegate = self
view.addGestureRecognizer(gestureRecognizer)
mySubViews.addSubview(view)
}
Don't use append if you're trying to add it in, and add it after you assign the gesture.

Related

UILabel set onClick

I'm building an application in Swift 3, so I want to call a function if I click on a particular UILabel, so I'm write this code but not works:
let tap = UITapGestureRecognizer(target: self, action: #selector(ViewController.tapFunction))
self.labelTemp.isUserInteractionEnabled = true
self.labelTemp.addGestureRecognizer(tap)
How can I render UILabel clickable ?
Set user interaction enabled for the UILabel and add the below code in the viewDidLoad()
self.label.isUserInteractionEnabled = true
let tap = UITapGestureRecognizer(target: self, action: #selector(self.labelTapped))
self.label.addGestureRecognizer(tap)
Add the tap action function as below :
#objc func labelTapped(_ gestureRecognizer: UITapGestureRecognizer) {
print("Label clicked")
}
Please make user that there is no other transparent view overlapping the UILabel in the view. If the UILabel is a part of another view then please make sure that the container View's user interaction is enabled.
Hope this helps.
Your selector should be an #objc func within self.
<#YourLabel#>.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.handleLabelTap)))
And when the user taps the label it will trigger:
#objc func handleLabelTap() {
// handle label tap here
}
You are lacking a function to trigger when the gesture touch is recognized. You need to add following:
let tap = UITapGestureRecognizer(target: self, action: #selector(tapFunction(_:)))
self.labelTemp.isUserInteractionEnabled = true
self.labelTemp.addGestureRecognizer(tap)
#objc func tapFunction(_ gestureRecognizer: UITapGestureRecognizer) {
// handle label tap here
}
Please ensure that You have connected the outlet to UILabel because I have created simple demo code by copy-paste your code and it is worked as expected.
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(tapFunction))
self.labelTemp.isUserInteractionEnabled = true
self.labelTemp.addGestureRecognizer(tap)
}
#objc func tapFunction() {
print("tapFunction")
}
I suggest, Please remove UILabel from UIViewController and add it again.
Download sample code
Note: - Please ensure that user-interaction of UILabelis enabled
First you need to add Tap Gesture into storyboard
Create Action of that particular gesture
override func viewDidLoad() {
super.viewDidLoad()
let tapOnLabel = UITapGestureRecognizer(target: self, action: #selector(self.tapGestireAction))
self.labelTemp.isUserInteractionEnabled = true
self.labelTemp.addGestureRecognizer(tapOnLabel)
}
#IBAction func tapGestureAction(_ sender: UITapGestureRecognizer) {
//Perform action
}

Triggering UITapGestureRecognizers from overlapping Views

I have one main view and 4 subviews of the mainview they all have their UITapGestureRecognizer, when I tap on one subview how can it be triggered both views. Example below,
if I tap to subview 1 desired log would be:
subview1 is clicked
MainView is clicked
My Code
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let mainGesture = UITapGestureRecognizer(target: self, action: #selector(mainGestureActivated))
self.view.addGestureRecognizer(mainGesture)
let subGesture = UITapGestureRecognizer(target: self, action: #selector(subViewGestureActivated))
self.subview1.addGestureRecognizer(subGesture)
}
#objc func mainGestureActivated(){
print("MainView Clicked")
}
#objc func subViewGestureActivated(){
print("Subview Clicked")
}
it prints only subview clicked! Is it possible to trigger both gestureRecognizers since main is encapsulating other.
First you should conform to UIGestureRecognizerDelegate in your VC, and then implement the delegate func of shouldRecognizeSimultaneouslyWith. Inside the function, you should detect if the gestureRecognizer, and the otherGestureRecognizer are the wants you want, and if so, you should allow them to work simultaneously,
Conform to delegate, and Declare gesture recognizers outside of viewDidLoad (because you need to access them in the delegate method later.)
var mainGestureRecognizer = UITapGestureRecognizer()
var subGestureRecognizer = UITapGestureRecognizer()
Initialize your recognizers, and set your VC as their delegate:
mainGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(mainGestureActivated))
subGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(subViewGestureActivated))
mainGestureRecognizer.delegate = self
subGestureRecognizer.delegate = self
Implement the delegate function mentioned above to allow simultaneous recognition for subView and mainView:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer == subGestureRecognizer && otherGestureRecognizer == mainGestureRecognizer {
return true
}
return false
}
And if you want it to work for 4 different subviews, then you should check with else if statements inside the delegate method.

I am defining gesture recognizers in an ImageView subclass but calling a method in the ViewController crashes with "unrecognized selector"

I am defining gesture recognizers in an ImageView subclass but calling a method in the ViewController crashes with "unrecognized selector". The gesture works if I define the gestures in the VC, but I'm trying to put the code in the custom view class to cut down on clutter.
Is this possible? This is what I'm doing:
class DetailPhotoImageView: UIImageView {
func setupGestures(){
let tapped = UITapGestureRecognizer(target: self, action: #selector(TripDetailVC.tapped(_:)))
tapped.numberOfTapsRequired = 2
addGestureRecognizer(tapped)
}
}
And then in the VC I have this function and call imageView.setupGestures()
func tapped(_ gesture:UIGestureRecognizer) {
if let tapGesture = gesture as? UITapGestureRecognizer {
switch tapGesture.numberOfTapsRequired {
case 2:
print("Worked")
default:
break
}
}
}
So going off of Randall Wang's suggestion, they are right, you should could also go about this another, space-saving way. Pass the viewController as a parameter and set the it as the target:
class DetailPhotoImageView: UIImageView, UIGestureRecognizerDelegate {
func setupGestures(_ target: UIViewController) {
guard let aTarget = target as? MyViewController else { return }
let tap = UITapGestureRecognizer(target: aTarget, action: #selector(aTarget.someAction(_:)))
tap.delegate = self
tap.numberOfTapsRequired = taps
addGestureRecognizer(tap)
}
}
And in your viewController class:
detailPhoto.setupGestures(self)
Make a protocol for your UIImageView that takes in the gesture and set your ViewController as the delegate (having the ImageView as a parameter as well is just good practice.. or common):
protocol DetailPhotoDelegate {
func detailPhoto(_ detailPhoto: DetailPhotoImageView, actionFor gesture: UITapGestureRecognizer)
}
Add a delegate variable to you UIImageView subclass and preform your protocol function when the view is tapped. Also, make sure your UIImageView is a UIGestureRecognizerDelegate.
class DetailPhotoImageView: UIImageView, UIGestureRecognizerDelegate {
var delegate: DetailPhotoDelegate?
func setupGestures() {
let tap = UITapGestureRecognizer(target: self, action: #selector(someAction(_:)))
tap.delegate = self
tap.numberOfTapsRequired = taps
addGestureRecognizer(tap)
}
func someAction(_ guesture: UITapGestureRecognizer) {
print("tap - subclass func")
guard let aDelegate = aDelegate else { assertionFailure(); return }
delegate.detailPhoto(self, actionFor: gesture)
}
}
Then add the protocol to your ViewController, set it as your DetailPhotoImageView's delegate somewhere prior to the action being implemented (I did it in viewDidLoad() for the example), and implement the protocol method however you wish:
class ViewController: UIViewController, DetailPhotoDelegate {
#IBAction weak var detailPhoto: DetailPhotoImageView!
override func viewDidLoad() {
super.viewDidLoad()
detailPhoto.delegate = self
}
// Mark: DetailPhotoDelegate
func detailPhoto(_ detailPhoto: DetailPhotoImageView, actionFor guesture: UITapGestureRecognizer) {
switch guesture.numberOfTapsRequired {
case 1: print("1 tap"); return
case 2: print("2 taps"); return
default: print("\(guesture.numberOfTapsRequired) taps"); return
}
}
I think target shouldn't be self

How can we add a UIGestureRecognizer to Outlet Collection?

I'm trying to add a tap gesture to an outlet collection of labels [UILabel], like this:
#IBOutlet var subLabels: [UILabel]!
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(HomePageViewController.selectSubLabel(tap:)))
tap.numberOfTapsRequired = 1
tap.cancelsTouchesInView = false
for i in (0..<(subLabels.count)) {
subLabels[i].addGestureRecognizer(tap)
}
}
func selectSubLabel(tap: UITapGestureRecognizer) {
print("Gesture Is WORKING!")
}
and i tried to add it on a single label in storyboard; but NONE are working.
Firstly, you need to allow user interaction on a label (it is turned off by default):
for i in (0..<(subLabels.count)) {
subLabels[i].isUserInteractionEnabled = true
subLabels[i].addGestureRecognizer(tap)
}
but gesture recognizer can observe for gestures only in one view.
So, there are two options:
I. Dedicated gesture recognizer for every label
for i in (0..<(labels.count)) {
let tap = UITapGestureRecognizer(target: self, action: #selector(selectSubLabel(tap:)))
labels[i].isUserInteractionEnabled = true
labels[i].addGestureRecognizer(tap)
}
II. One gesture recognizer for the parent view of the labels
override func viewDidLoad() {
super.viewDidLoad()
for i in (0..<(labels.count)) {
subLabels[i].isUserInteractionEnabled = true
}
let tap = UITapGestureRecognizer(target: self, action: #selector(selectSubLabel(tap:)))
view.addGestureRecognizer(tap)
}
func selectSubLabel(tap: UITapGestureRecognizer) {
let touchPoint = tap.location(in: view)
guard let label = subLabels.first(where: { $0.frame.contains(touchPoint) }) else { return }
// Do your stuff with the label
}
Please check the User Interaction Enabled Attribute of your UIlabel's in Attribute inspector of Xcode. User Interaction Enabled must be ticked for detecting the tap. Please see the picture below,

Swift 3 UITapGestureRecognizer not receiving tap

I want to add a gesture recognizer to one of my views to detect taps,
here is my code:
class DateTimeContainer: UIView, UIGestureRecognizerDelegate {
override func awakeFromNib() {
let gesture = UITapGestureRecognizer(target: self, action: #selector(self.onTap))
gesture.delegate = self
self.addGestureRecognizer(gesture)
}
func onTap(_ gestureRecognizer: UITapGestureRecognizer) {
openDatePicker()
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if touch.view?.tag != self.datePickerTag && !self.isDatePickerOpen() {
return true
}
return false
}
}
When I tap on my view the code enters into the gestureRecognizer shouldReceive method and enters the condition for which it returns true.
But the onTap method is never called, can someone tell me why?
EDIT
Adding self.isUserInteractionEnabled = true I managed to get it working.
But it had a strange behaviour: it was like it received the tap only in the main view and not in subviews.
So to make it simple I solved by adding a button inside my view and by using:
self.button?.addTarget(self, action: #selector(onTap), for: .touchUpInside)
You may have forgotten to do this:
self.isUserInteractionEnabled = true
Also, typically the UIViewController subclass is usually the target and contains the method used for the tap gesture.
You don't need to set a delegate for a tap gesture (usually)
You can You Gesture
let recognizer = UITapGestureRecognizer(target: self,action:#selector(self.handleTap(recognizer:)))
userImage.isUserInteractionEnabled = true
recognizer.delegate = self as? UIGestureRecognizerDelegate
userImage.addGestureRecognizer(recognizer)
And method call selector
func handleTap(recognizer:UITapGestureRecognizer) {
// do your coding here on method
}
Try Writing the codes
let gesture = UITapGestureRecognizer(target: self, action: #selector(self.onTap))
gesture.delegate = self
self.addGestureRecognizer(gesture)
inside init block not in awakeFromNib()
I noticed that you are adding your gesture on a UIView. As such, at that ViewController where you have added this ContainerView, inside the viewDidLoad method, add this line of code.
override func viewDidLoad() {
super.viewDidLoad()
self.YourContainerView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(YourCurrentViewController.openDatePicker())))
self.YourContainerView.isUserInteractionEnabled = true
}
Note. Do not forget to add isUserInteractionEnabled to true.

Resources