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!
}
Related
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.
My goal is to perform segue when i tap on imageview of that cell. but the error does not appear when i use addTarget on a button.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
cell.imageView.userInteractionEnabled = true
let tapImageView = UITapGestureRecognizer(target: self, action: #selector(HomeFeedViewController.tapImageView(_:)))
cell.imageView.addGestureRecognizer(tapImageView)
return cell as CastleCell
}
func tapImageView(sender: AnyObject) {
let center = sender.center
let point = sender.superview!!.convertPoint(center, toView:self.tableView) //line of error
let indexPath = self.tableView.indexPathForRowAtPoint(point)
let cell = self.tableView.cellForRowAtIndexPath(indexPath!) as! CastleCell
performSegueWithIdentifier("SegueName", sender: self)
}
The line of error is let point =...
The error i get is:
fatal error: unexpectedly found nil while unwrapping an Optional value
(lldb)
but the error does not appear when i use addTarget on a button. what could be wrong? thanks.
Am not really fond of playing with points and Superview. What is can suggest is to make a class for UITapGestureRecognizer as follows which can hold extra data. In your case it would be an index path
class CustomGesture: UITapGestureRecognizer {
let indexPath:NSIndexPath? = nil
}
And then in your didSelect you can add the index path to the newly created CustomGesture class which be would be like:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
cell.imageView.userInteractionEnabled = true
let tapImageView = CustomGesture(target: self, action: #selector(HomeFeedViewController.tapImageView(_:)))
tapImageView.indexPath = indexPath// Add the index path to the gesture itself
cell.imageView.addGestureRecognizer(tapImageView)
return cell as CastleCell
}
Now since you have added the indexPath you don't need to play around with super view's and you can access the cell like this:
func tapImageView(gesture: CustomGesture) {
let indexPath = gesture.indexPath!
let cell = self.tableView.cellForRowAtIndexPath(indexPath!) as! CastleCell
performSegueWithIdentifier("SegueName", sender: self)
}
I have a tableview with custom cell that contain 1 label and 1 button. The button is use for everytime I tap it, increase the number inside label, example if my label value is 0 then everytime I tap the button it is increase by 1
The problem is I have a lot of data, example I have 20 and everytime I set a value for the label when I scroll all the value is changed
this is my code:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("testCell", forIndexPath: indexPath)
let pos = cell.viewWithTag(6) as! UIButton
pos.addTarget(self, action: #selector(testTableViewController.pos(_:)), forControlEvents: .TouchUpInside)
pos.tag = indexPath.row
return cell
}
func pos(sender: UIButton) {
let position: CGPoint = sender.convertPoint(CGPointZero, toView: self.tableView)
let indexPath = self.tableView.indexPathForRowAtPoint(position)
let cell: UITableViewCell = self.tableView.cellForRowAtIndexPath(indexPath!)! as UITableViewCell
let countNo = cell.viewWithTag(7) as! UILabel
var counter:Int = Int(countNo.text!)!
counter = counter + 1
countNo.text = String(counter)
}
please help me how to solve this problem. I have search a lot of web to find the answer, example like make dequeueReusableCellWithIdentifier nil but in Swift 2 dequeueReusableCellWithIdentifier never nil and a lot more with no one is work
I need some hint how to solve this problem
Here is your answer.
Initialise one array having number of objects same as your tableview's number of row. Initially insert value "0" or blank string (if do not want to display any number in label initially) for each object.
ex,
var arrValues:NSMutableArray = NSMutableArray()
for _ in 1...numberOfrow
{
arrValues.addObject("0")
}
Now in cellForRowAtIndexPath method , display text of label from this array
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("testCell", forIndexPath: indexPath)
let pos = cell.viewWithTag(6) as! UIButton
**let countNo = cell.viewWithTag(7) as! UILabel
countNo.text = String(arrValues.objectAtIndex(indexPath.row))**
pos.addTarget(self, action: #selector(testTableViewController.pos(_:)), forControlEvents: .TouchUpInside)
pos.tag = indexPath.row
return cell
}
On button click method replace object at particular index with incremented number. See your updated method as below.
func pos(sender: UIButton) {
let position: CGPoint = sender.convertPoint(CGPointZero, toView: self.tableView)
let indexPath = self.tableView.indexPathForRowAtPoint(position)
let cell: UITableViewCell = self.tableView.cellForRowAtIndexPath(indexPath!)! as UITableViewCell
let countNo = cell.viewWithTag(7) as! UILabel
var counter:Int = Int(countNo.text!)!
counter = counter + 1
countNo.text = String(counter)
**arrValues.replaceObjectAtIndex(indexPath.row, withObject: String(counter))**
}
So in cellForRowAtIndexPath , it will display updated number in label text
I have a tableView with list of persons. I want to select multiple cells. I've created a dictionary to store selected cells (screenshot).
var checkedSubjects: [Person: Bool] = [Person: Bool]()
Then when I select a cell it shows a checkmark near the cell and save it in my array (screenshot).
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell: SearchTableViewCell = tableView.dequeueReusableCellWithIdentifier("CELL", forIndexPath: indexPath) as! SearchTableViewCell
cell.tintColor = UIColor(hex: 0x3f51b5)
cell.subjectNameLabel.text = subjects[indexPath.row].name
cell.subjectDescriptionLabel.text = "(\(subjects[indexPath.row].type))"
let person = Person(id: subjects[indexPath.row].id, name: subjects[indexPath.row].name, type: subjects[indexPath.row].type)
if checkedSubjects[person] != nil {
cell.accessoryType = checkedSubjects[person]! ? .Checkmark : .None
}
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: false)
let index = indexPath.row
let person = Person(id: subjects[index].id, name: subjects[index].name, type: subjects[index].type)
if tableView.cellForRowAtIndexPath(indexPath)!.accessoryType == .Checkmark {
tableView.cellForRowAtIndexPath(indexPath)!.accessoryType = .None
checkedSubjects[person] = false
counter--
} else {
tableView.cellForRowAtIndexPath(indexPath)!.accessoryType = .Checkmark
checkedSubjects[person] = true
counter++
}
if counter > 0 {
saveBtn.enabled = true
let text = counter == 1 ? "Add \(counter) person" : "Add \(counter) persons"
saveBtn.setTitle(text, forState: UIControlState.Normal)
} else {
saveBtn.enabled = false
saveBtn.setTitle("Choose persons", forState: UIControlState.Normal)
}
}
But when I press this cell one more time I want it to return to default view. It removes checkmark but the text doesn't take empty space (screenshot). The trailing constraint of the label is set to container margin.
I've tried to reloadData() of tableView in didSelectRowAtIndexPath but it doesn't helped.
Any ideas how I can solve this issue?
I think the problem here is that you're trying to manipulate the physical cell in your didSelectRow implementation. This is the wrong way to go about things. Do not attempt to change or even read the accessory type of a cell in your didSelectRow implementation! Instead, operate there entirely on the model (checkedSubjects) and reload the affected row, so that the view picks up changes to the model (because cellForRow will be called).
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.