Click on ImageView and execute function WITHOUT selectors in Swift/Xcode - ios

I have a tableView with cells. Each cell has an imageView. When I click on the imageView, I want to execute a function with parameters that I pass in.
The only way that I know how to work with clicking on imageViews is something like this:
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(printNumber))
cell.imageView.addGestureRecognizer(gestureRecognizer)
#objc func printNumber(){
print("4")
}
Now, imagine this exact same thing, but I want to pass the number to print into the function
I've seen a million different posts about how you can't pass parameters into selectors, so I'm not sure what to do in this case.
I want to do something like this (I know you can't do this)
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(printNumber(num: 4))
cell.imageView.addGestureRecognizer(gestureRecognizer)
#objc func printNumber(String: num){
print(num)
}
I need to do this because every button will have a different output when pressed, depending on some other variables in the cell.

You don't need to pass in the number. Assuming the recognizer is on the image view you:
Get the gestures.view (this is the image view)
Walk up the responder chain to find the parent tableview cell and table view
Ask the tableview for the index path of the tableview cell (this is basically he number you wanted to pass in)
Call your function with the number
As for walking up the responder chain generically:
extension UIResponder {
func firstParent<Responder: UIResponder>(ofType type: Responder.Type ) -> Responder? {
next as? Responder ?? next.flatMap { $0.firstParent(ofType: type) }
}
}
so your code is:
guard let cell = recognizer.view?.firstParent(ofType: UITableViewCell.self),
let tableView = recognizer.view?.firstParent(ofType: UITableView.self),
let indexPath = tableview.indexPath(for: cell) else {
return
}
// Do stuff with indexPath here

Use the accessibilityIdentifier property, only if you still want to use the #selector, so that UIGestureRecognizer can read the value of the value object.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! YourTableViewCell
…
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(printNumber))
cell.imageView.accessibilityValue = String(4)
cell. imageView.isUserInteractionEnabled = true
cell.imageView.addGestureRecognizer(gestureRecognizer)
…
return cell
}
#objc func printNumber(sender: UITapGestureRecognizer) {
if let id = sender.view?.accessibilityIdentifier {
print(tag)
}
}

Related

One click on image inside cell of UITableView

I have two images inside one cell of uitableview, these images shows to images from an external server and each tag of them has an id of item which this image represent, I need if I clicked on this image move user to new view controller which show details of this item, I force a problem, where user need to double click to show details instead of one click, the following my code:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = self.tableView.cellForRow(at: indexPath as IndexPath) as! prodctCell
Id1stMove = cell.image1st.tag
let tapGesture = UITapGestureRecognizer (target: self, action: #selector(ItemsController.imgTap))
cell.image1st.addGestureRecognizer(tapGesture)
cell.image1st.isUserInteractionEnabled = true
let cell1 = self.tableView.cellForRow(at: indexPath as IndexPath) as! prodctCell
Id2ndMove = cell1.image2nd.tag
let tapGesture1 = UITapGestureRecognizer (target: self, action: #selector(ItemsController.imgTap1))
cell1.image2nd.addGestureRecognizer(tapGesture1)
}
func imgTap()
{
let secondViewController = self.storyboard?.instantiateViewController(withIdentifier: "testViewController") as? testViewController
let navController = UINavigationController(rootViewController: secondViewController!)
navController.setViewControllers([secondViewController!], animated:true)
self.revealViewController().setFront(navController, animated: true)
revealViewController().pushFrontViewController(navController, animated: true)
secondViewController?.movmentId = Id1stMove
updateCount(itemId: Id1stMove)
}
Yesterday itself I created sample and tried.I got the solution.But I could not post my answer immediately as I had some work.
Now I will give you my answer.I don't expect reputation for my below answer.
When you click or tap the image first time,it navigates.
You don't need to add TapGestureRecognizer for imageView in didSelectRowAt method.You need to add TapGestureRecognizer for image View in cellForRowAt method.
import UIKit
class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {
let mobiles: [String] = ["iPhone", "Android"]
let images: [String] = ["iPhone.png", "android.png"]
#IBOutlet var tableViewImageTapping: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// number of rows in table view
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.mobiles.count
}
// create a cell for each table view row
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
var cell:UITableViewCell? = tableView.dequeueReusableCell(withIdentifier: "cell")
if (cell == nil) {
cell = UITableViewCell(style:UITableViewCellStyle.default, reuseIdentifier:"cell")
}
cell?.textLabel?.text = self.mobiles[indexPath.row]
let strImageName = images[indexPath.row]
cell?.imageView?.image = UIImage(named: strImageName)
cell?.imageView?.tag = indexPath.row
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(imageTapped(tapGestureRecognizer:)))
tapGestureRecognizer.numberOfTapsRequired = 1
cell?.imageView?.isUserInteractionEnabled = true
cell?.imageView?.addGestureRecognizer(tapGestureRecognizer)
return cell!
}
// method to run when table view cell is selected
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("You tapped table view cell index is \(indexPath.row).")
}
// method to run when imageview is tapped
func imageTapped(tapGestureRecognizer: UITapGestureRecognizer)
{
let imgView = tapGestureRecognizer.view as! UIImageView
print("your taped image view tag is : \(imgView.tag)")
if (imgView.tag == 0) //Give your image View tag
{
//navigate to next view
}
else{
}
}
}
Output Screenshot
Printed results are
When you click the first image in first click
Then when you click the second image in first click
You need to execute this code in cellForRowAt indexPath instead did select.
As you said you want to use image id from the tag value I suggest below change in addition of adding code in cellForRowAt indexPath:
Change tapGesture code as below:
let tapGesture = UITapGestureRecognizer (target: self, action: #selector(imgTap(tapGesture:)))
And imgTap function:
func imgTap(tapGesture: UITapGestureRecognizer) {
let imgView = tapGesture.view as! UIImageView
let idToMove = imgView.tag
//Do further execution where you need idToMove
}
You are assigning the gesture recognizers in the "did select row at index path" method, this means that the user must select (tap) a cell for the gesture recognizers to be assigned to the images, and then the user must tap the image for those recognizers to react by calling "imgTap()", those are the two taps.
What you should do instead is assign the gesture recognizers in the "cell for row at index path" method, so when you create each cell you also create the gesture recognizers, that way when the user taps an image for the first time the tap is recognized and "imgTap()" is called.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath) as! CustomCell
// Configure the cell...
let tapGesture1 = UITapGestureRecognizer (target: self, action: #selector(ItemsController.imgTap))
cell.image1st.addGestureRecognizer(tapGesture1)
let tapGesture2 = UITapGestureRecognizer (target: self, action: #selector(ItemsController.imgTap))
cell.image2nd.addGestureRecognizer(tapGesture2)
return cell
}
I also recommend changing the code a little so you can call "imgTap()" with a parameter (the id of what was tapped), instead of having 2 methods "imgTap()" and "imgTap1()".
Please don't use force unwrap as! prodctCell
Please use name swift naming conventions
Don't use .tag cell is usually reused it might break in some edge cases
Now back to your question, you already have a custom cell that holds the image.
you have several possibilities
Add the gesture recogniser to the imageView
you can change that to a button
Add a button over the image view (no title or image for it)
Then create an #IBAction for the button/gesture recogniser, and a delegate method that will call the main viewController where you can pass all the data you need from the cell to instantiate the second VC
Update
I would advice against adding the logic of handling the tap in the cellForRow the viewController is not meant to handle and manage his child logic.

Detecting Tap Events for UILabel inside UITableViewCell using UITapGestureRecognizer

I have a UITableViewDataSource with the following
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: OutletDetails.cellIdentifier) as! OutletDetails
cell.selectionStyle = .none
cell.isUserInteractionEnabled = true
cell.location = "Some location will be here"
let tap = UITapGestureRecognizer(target: self, action: #selector(locationClicked))
tap.cancelsTouchesInView = false
tap.numberOfTapsRequired = 1
cell.location.addGestureRecognizer(tap)
}
where cell.location is a UILabel object. What I'm trying to do here is to detect tap events on the UILabel. I looked all over the Internet and everyone is suggesting this method, however, this code is not working in my case. The method locationClicked is not being called at all. Can anyone tell me what's wrong with my code?
Edit
One more thing, is it a good idea to do it this way memory-wise? I mean if we have a long list, then many UIGestureRecognizer objects will be generated for each cell. This is because the method will be called a lot while scrolling the items.
Add tap gesture to the object and enable its user interaction. Yes you can take button as well.
//Adding tap gesture
let cellNameTapped = UITapGestureRecognizer(target: self, action: #selector(nameTapped))
nameLabel.isUserInteractionEnabled = true// UILabel made available for touch interaction
nameLabel.addGestureRecognizer(cellNameTapped) //gesture added
//Method called on touch of nameLabel
#objc func nameTapped(tapGestureRecognizer: UITapGestureRecognizer){
//print(tapGestureRecognizer.view)
}
Since you're dequeuing a cell, you will need to somehow get a reference to the UITapGestureRecognizer and either remove it or reuse it. Otherwise every time you reuse a cell, you will be laying another recognizer onto the one that is already on the cell. If you're subclassing the UITableViewCell you can just add the recognizer as a property.
However, using the code you posted I'm suggesting you use a UIButton and add a tag so you can get a reference to it later. You can set the bounds of the button equal to the bounds of the label. Try something like this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: OutletDetails.cellIdentifier) as! OutletDetails
cell.selectionStyle = .none
cell.isUserInteractionEnabled = true
cell.location = "Some location will be here"
// If we don't already have a button on our cell, create one and set the tag
if cell.viewWithTag(103) as? UIButton == nil {
let newButton = UIButton(frame: cell.location.bounds)
newButton.tag = 103
newButton.addTarget(target: self, action: #selector(locationClicked), for: .touchUpInside)
}
}

Tap label in table view?

I have a label I want to tap on using addGestureRecognizer. I put it in cellForRowAtIndexPath but when I do print(label.text), it prints a label from another cell. But when I put it in didSelectRowAtIndexPath, it prints out the right label for that cell.
What is the best way to fix this?
Here is the code:
var variableToPass: String!
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell : MainCell! = tableView.dequeueReusableCellWithIdentifier("MainCell") as! MainCell
variableToPass = label1.text
cell.label1.userInteractionEnabled = true
let tapLabel = UITapGestureRecognizer(target: self, action: #selector(ViewController.tapLabel(_:)))
cell.label1.addGestureRecognizer(tapLabel)
return cell as MainCell
}
func tapCommentPost(sender:UITapGestureRecognizer) {
print(variableToPass)
}
I think you forget to set the tap.tag = indexPath.row for identify which cell you tabbed for Find, for example
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell : MainCell! = tableView.dequeueReusableCellWithIdentifier("MainCell") as! MainCell
variableToPass = label1.text
cell.label1.userInteractionEnabled = true
let tapLabel = UITapGestureRecognizer(target: self, action: #selector(ViewController.tapLabel(_:)))
cell.label1.tag = indexPath.row
tapLabel.numberOfTapsRequired = 1
cell.label1.addGestureRecognizer(tapLabel)
return cell as MainCell
}
func tapLabel(sender:UITapGestureRecognizer) {
let searchlbl:UILabel = (sender.view as! UILabel)
variableToPass = searchlbl.text!
print(variableToPass)
}
There are several issues with your current code: (1) You're setting variableToPass in cellForRowAtIndexPath:, so assuming label1.text is the label belonging to the cell, as the table loads, the variableToPass will always contain the label text of the last loaded cell. (2) cellForRowAtIndexPath: can be called multiple times for each cell (for example, as you scroll) so you could be adding multiple gesture recognizers to a single cell.
In order to resolve issue #1, remove the variableToPass variable entirely and instead directly access the gesture's label view. In order to resolve issue #2, I'd recommend adding the gesture recognizer to your custom MainCell table view cell, but if you don't want to do that, at least only add a gesture recognizer if one isn't already there.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("MainCell") as! MainCell
if cell.label1.gestureRecognizers?.count == 0 {
cell.label1.userInteractionEnabled = true
let tapLabel = UITapGestureRecognizer(target: self, action: #selector(ViewController.tapCommentPost(_:))) // I assume "tapLabel" was a typo in your original post
cell.label1.addGestureRecognizer(tapLabel)
}
return cell
}
func tapCommentPost(sender:UITapGestureRecognizer) {
print((sender.view as! UILabel).text) // <-- Most important change!
}

How to get label on button click in tableview cell for prepareForSegue

I have a tableview. i need to get the label "usernameLabel" from that cell and assign a variable to them. I need to pass that variable to prepareForSegue. The problem is the label is the wrong label from the wrong cell.
here is what i have:
var username: String!
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell : MainCell! = tableView.dequeueReusableCellWithIdentifier("MainCell") as! MainCell
username = usernameLabel.text
cell.button.userInteractionEnabled = true
let tapButton = UITapGestureRecognizer(target: self, action: #selector(ViewController.tapLabel(_:)))
cell.button.addGestureRecognizer(tapButton)
return cell as MainCell
}
func tapButton(sender:UITapGestureRecognizer) {
performSegueWithIdentifier("ViewToView2Segue", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "ViewToView2Segue" {
let userProfileViewController = segue.destinationViewController as! SecondViewController
secondViewController.usernamePassed = usernamePassed
}
}
simplified question: i need to pass the label.text to another view controller via segue. but currently, it is getting the label from the wrong cell.
cellForRowAtIndexPath method will be multiple times so assigning value in that method will not work, also why are you using tapGesture on button instead of adding action to button, try to change your cellForRowAtIndex like this.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell : MainCell! = tableView.dequeueReusableCellWithIdentifier("MainCell") as! MainCell
cell.button.setTitle( usernameLabel.text, forState: .Normal)
cell.button.addTarget(self, action: #selector(self.buttonAction(_:)), forControlEvents: .TouchUpInside)
return cell
}
Now add this buttonAction function inside your ViewController
func buttonAction(sender: UIButton) {
let center = sender.center
let point = sender.superview!.convertPoint(center, toView:self.tableView)
let indexPath = self.tableView.indexPathForRowAtPoint(point)
let cell = self.tableView.cellForRowAtIndexPath(indexPath) as! MainCell //Add superview on the basis of your button hierarchy in the cell
username = cell.usernameLabel.text
print(username)
performSegueWithIdentifier("ViewToView2Segue", sender: self)
}
You should not save state data in cells. You should have the info that you want in your model (probably an array). When the user taps a cell you should use the indexPath of the selected cell to fetch the info from the model, not from a label on the cell.
(Look up the MVC design pattern for background. A table view cell is a view object, and should not store data. That's the model's job.)

How to send cell number to the function in TableViewCell

I'd like to know how to get cell number(indexPath.row) in the following tapPickView function. topView is on the UITableViewCell, and pickView is on the topView. If pickView is tapped, tapPickView is activated.
override func tableView(tableView: UITableView,
cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(
"QuestionAndAnswerReuseIdentifier",
forIndexPath: indexPath) as! QuestionAndAnswerTableViewCell
cell.topView.pickView.userInteractionEnabled = true
var tap = UITapGestureRecognizer(target: self, action: "tapPickView")
cell.topView.pickView.addGestureRecognizer(tap)
return cell
}
func tapPickView() {
answerQuestionView = AnswerQuestionView()
answerQuestionView.questionID = Array[/*I wanna put cell number here*/]
self.view.addSubview(answerQuestionView)
}
First of all, you need to append : sign to your selector upon adding gesture recognizer in order for it to get the pickView as its parameter.
var tap = UITapGestureRecognizer(target: self, action: "tapPickView:")
Besides that, cells are reusable objects, so you should prevent adding same gesture again and again to the same view instance by removing previously added ones.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("QuestionAndAnswerReuseIdentifier", forIndexPath: indexPath) as! QuestionAndAnswerTableViewCell
cell.topView.pickView.userInteractionEnabled = true
cell.topView.pickView.tag = indexPath.row
for recognizer in cell.topView.pickView.gestureRecognizers ?? [] {
cell.topView.pickView.removeGestureRecognizer(recognizer)
}
cell.topView.pickView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: "tapPickView:"))
return cell
}
While populating the cell, you can set tag value of the pickView as indexPath.row so you can easily query that by cellForRowAtIndexPath(_:).
cell.topView.pickView.tag = indexPath.row
Assuming you already know the section of the cell you tap on. Let's say it is 0.
func tapPickView(recognizer: UIGestureRecognizer) {
let indexPath = NSIndexPath(forRow: recognizer.view.tag, inSection: 0)
if let cell = self.tableView.cellForRowAtIndexPath(indexPath) {
print("You tapped on \(cell)")
}
}
Hope this helps.
Assuming that this was not as simple as didSelectRowAtIndexPath, which I strongly recommend to first look into, passing the information to your method could look like this:
#IBAction func tapPickView:(sender: Anyobject) {
if let cell = sender as? UITableViewCell {
let indexPath = self.tableView.indexPathForCell(cell: cell)
println(indexPath)
}
}
Use didSelectRowAtIndexPath delegate method.

Resources