I have a DynamicTableView and every Cell has an ImageView. How do I make an Action with knowing the indexPath.row?
The only way I know is to make a TapGestureRecognizer – that works but I then don't know which row I clicked.
So I need something like DidSelectRowAt from the TableView:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {}
This Action should only happen when I press the ImageView not the whole Row, therefore I can't use DidSelectRowAt from the TableView.
Thats what I did right now:
class DownloadsViewController: UITableViewController {
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let tap = UITapGestureRecognizer(target: self, action: #selector(DownloadsViewController.tappedMe))
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! DownloadsViewCell
cell.fileInfo.image = UIImage(named: "detail1")
cell.fileInfo.addGestureRecognizer(tap)
cell.fileInfo.isUserInteractionEnabled = true
}
func tappedMe(){
print(arrayDocuments(IndexPath.row))
}
}
My whole DownloadViewContoller Class Code is here
My DownloadViewCell Screenshot is here
Does anyone have an Idea?
Thank you!
Add a property to your cell: var indexPath: IndexPath.
In cellForRowAtIndexPath: do cell.indexPath = indexPath
In your cell subclass add tapGestureRecognizer to UIImageView which triggers method in cell subclass.
Make a delegate protocol for your cell:
protocol YourCellDelegate: class {
func imagePressed(indexPath: IndexPath)
}
Make your ViewController adopt this protocol.
In your cell subclass add property weak var delegate: YourCellDelegate?
In cellForRowAtIndexPath: do cell.delegate = self
In cell tapRecognizer selector call delegate method delegate?.imagePressed(indexPath: indexPath)
In your viewController you'll receive imagePressed method call with indexPath as an argument.
Just set the ImageView like you had before, and then just add a blank Button over it and use the method from the Answer from Ilya V. with the IBAction
Did you try to get cell by
recognizer.view
on tap gesture method and
func indexPath(for cell: UITableViewCell) -> IndexPath?
returns an index path representing the row and section of a given table-view cell.
Here is a solution that uses a custom UIImageView subclass.
Create a subclass of UIImageView - MyImageView (or something else more appropriate)
Create a delegate for the subclass - MyImageViewDelegate
Add a delegate method called myImageViewTapped(imageView: MyImageView)
Override the touchesEnded method to call the delegate method
Add a property in MyImageView called indexPath that stores the index path of the cell that this image view is in.
In your table cells, use MyImageView instead of a regular UIImageView.
Set the delegate of the MyImageView to self i.e the VC.
Set isUserInteractionEnabled to true
In cellForRowAtIndexPath, set the image view's indexPath to the indexPath parameter
Now implement the delegate method and in the method, you will know which image view is tapped by accessing indexPath!
You can use property "tag" imageView to save indexPath.row and use it later in the selector
class DownloadsViewController: UITableViewController {
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! DownloadsViewCell
cell.fileInfo.image = UIImage(named: "detail1")
cell.fileInfo.isUserInteractionEnabled = true
let tap = UITapGestureRecognizer(target: self, action: #selector(tappedMe(_ :)))
cell.fileInfo.addGestureRecognizer(tap)
}
#objc func tappedMe(_ sender: AnyObject){
print("Tap to imageView in cell No. - \(sender.view.tag)")
}
}
Related
I have an app with a UICollectionView. Each custom cell has a circular progress view and a button that's supposed to up its progress, but I can't figure out how to change the properties for the specific cell's subviews inside the button action.
I've tried
adding a target to the button inside the "cellForItemAt indexPath" function, but then how to call for that specific cell inside the target's function.
adding a IBAction inside the custom cell class, but same problem again
Basically the problem is how can you call for a specific cell at a certain index path outside the "cellForItemAt" function?
You can setup your UICollectionViewCell subclass something like this:
class MyCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var button: UIButton!
#IBOutlet weak var progressView: UIProgressView!
var buttonAction: (() -> Void)?
#IBAction func didPressButton() {
buttonAction?()
}
}
and in your UITableViewDelegate
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "your identifier", for: indexPath) as! MyCollectionViewCell
cell.buttonAction = {
//Do whatever you wish
}
...
...
return cell
}
Adding a target to the button inside the cellForItemAt indexPath function, also send the index path as a tag with the button function like:-
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "your identifier", for: indexPath) as! MyCollectionViewCell
cell.buttonAction = {
//Do whatever you wish
}
...
...
**cell.button.tag = indexPath.row**
return cell
}
Then the function in which you have set as target, you need to update that particular cell to update UI by writing the below code
[self.collectionView reloadItemsAtIndexPaths:#[Sender.tag]];
In my UITableViewCell have button AddToCart buttons. As if my UITableView data is more than 10 means I have to scroll to see all data. So now if I will on first button of first UITableViewCell as I scroll down to see all records of tableview than automatically last or second last button will also click I am unable to find the problem why this is happening
I am implementing first time this type of functionality so got stuck to resolve the problem
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 13
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "tblCell") as! ProductTableViewCell
cell.btnAddToCart.tag = indexPath.row
cell.btnAddToCart.addTarget(self, action: #selector(addToCartDell(sender:)), for: .touchUpInside)
return cell
}
This function is used for hide and show Add To Cart button option.
#objc func addToCartDell(sender: UIButton) {
let tagVal = sender.tag
let indexPath = IndexPath(item: tagVal, section: 0)
if let cell = tblProduct.cellForRow(at: indexPath) as? ProductTableViewCell {
cell.btnAddToCart.isHidden = true
}
}
Cells are reused. You don't save the hidden state of the cell so when a cell is reused the latest state is preserved.
In Swift the most efficient and reliable solution is to save the state added to cart in the data model and use a callback closure to update the UI in cellForRow.
In the data model add a property addedToCart, it's assumed that a custom struct or class is used as data model
var addedToCart = false
In ProductTableViewCell add the callback variable and an IBAction. Connect the IBAction to the button
var callback : (()->())?
#IBAction func buttonPressed(_ sender : UIButton) {
callback?()
}
In the controller in cellForRow handle the callback, products represents the data source array
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "tblCell") as! ProductTableViewCell
let product = products[indexPath.row]
cell.btnAddToCart.isHidden = product.addedToCart
cell.callback = {
product.addedToCart = true
cell.btnAddToCart.isHidden = true
}
return cell
}
No tags, no target/action, no protocols, no extra work in willDisplayCell .
This issue is turning up because we re-user same cell for displaying any further rows that was not visible yet.
you may implement this method to correct the display of any further cells
optional func tableView(_ tableView: UITableView,
willDisplay cell: UITableViewCell,
forRowAt indexPath: IndexPath)
Customize the cell as you want it to appear in this delegate method. This delegate method is called just before the cell is displayed, so you can do any customization here and it will turn up in the UI as per your customization.
If we go deep in to implementation.
There must be a model that keeps the state addedToCart in this model on basis of the button tapped in a particular row and use this same model's addedToCart (model.addedToCart) to show hide the button in delegate method.
I have a button inside a tableview cell (as shown in the below image), when I click the button,
didSelectRowAt indexpath
is not being triggered, could some one please suggest how I could do this ?
Please note:
I am performing a set of actions on click of the button, in addition I would also like
didselectRowAt indexPath
to be triggered.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("Table view cell has been clicked")
}
When you have multiple views in the cell they may receive the touch event and not the "cell" so didSelectRowAt will not be triggered. In that case add view.isUserInteractionEnabled = false to each view, label or button.
Put the outlet to the button inside a custom cell class. Then set tag and add selector for button in cellForItemAt. This example is for colletionView just change to suit tableView.
If you want the button in each cell this is how you have to do it. Adding a static button to a single cell won't call didSelectItemAt because your tapping a button that doesn't have reference to a reusable cells indexPath.
This way we send the button.tag tot he function so we know which cell the button is related to.
class MyClassViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
.... // Stuff
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
....// Create Cell
cell.deleteCellButton.tag = indexPath.item
cell.deleteCellButton.addTarget(self, action: #selector(MyClassViewController.deleteCellButtonTapped(_:)), for: .touchUpInside)
return cell
}
func deleteCellButtonTapped(_ sender: Any) {
... // Stuff
print("Selector called")
}
}
If you are adding button or gesture on UITableViewCell , didselectRowAt indexPath not invoked . You can add button on remaining UI UITableViewCell than clear color apply on button . User didn't show button and you can perform didselectRowAt indexPath method task . If you wanna indexpath in button method code give below.
func btnChildDropDown(sender:UIButton)
{
let cell = sender.superview?.superview as! ChildAgeTableViewCell
let indexPath = self.tblViewAddRooms.indexPath(for: cell)
}
I have my TableViewCell inside it's own class called VersionCellVC.
I have my UIImageView completely covering up the cell; so the users have no choice but to tap on the UIImageView, not the TableViewCell.
I want to perform a segue or present a vc, and I have already created the gesture recognizer and the function it calls. however, since I am in a custom subclass of UITableViewCell, I am being told by the system that I am not allowed to use the present(vc:animate:complete) nor the prepare(forSegue:..:)) functions since they are not members of the UITableViewCell class.
How do I solve this without changing the UIImageView to a UIButton?
what about something like this (in your viewcontroller that contains the tableview)?
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
cell.myImageView?.isUserInteractionEnabled = true
cell.myImageView?.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(imageViewTapped(imageView:))))
return cell
}
func imageViewTapped(imageView: UIImageView) {
if let indexPath = tableView.indexPathForRow(at: imageView.convert(.zero, to: tableView)) {
print("user tapped imageview at indexPath \(indexPath)")
}
}
I have a custom class for a table cell that is connected to a switch within the table cell (with an action) and I want to be able to to communicate to the TableViewController that the action happened as well as the path of the cell. The way that initially came to mind was if I could use some function in UITableViewCell to get the TableViewController of the table the cell is part of, as my custom class is (rather obviously) a subclass of UITableViewCell. Please tell me if I'm missing something.
To get a reference to the containing view controller, I add a weak property on the cell subclass.
#property (nonatomic, weak) UITableViewController *viewController;
You can assign this value in cellForRowAtIndexPath:
For accessing each cell in TableView
override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as YourTableViewCell
cell.mainTextLabel.text = self.venueService.mainCategoriesArray()[indexPath.row]
return cell
}
For getting selected cell in TableView
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let indexPath = tableView.indexPathForSelectedRow();
let currentCell = tableView.cellForRowAtIndexPath(indexPath) as UITableViewCell;
print(currentCell)
}
Reaching from TableViewCell to TableView
self.superview //// self is TableViewCell and its superview represents TableViewController
hope that helps
You should make the ViewController the target of the switch action.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("SwitchCell", forIndexPath: indexPath) as! SwitchTableViewCell
cell.onSwitch.addTarget(self, action: Selector("cellSwitchDidChange:"), forControlEvents: .ValueChanged)
cell.label.text = "This is a Switch"
return cell
}
func cellSwitchDidChange(sender: UISwitch) {
let origin = sender.convertPoint(CGPointZero, toView: tableView)
let indexPath = tableView.indexPathForRowAtPoint(origin)!
// do something
}