Access ViewController from action in TableViewCell - ios

I have ViewController with TableView. In each cell of this TableView there are few buttons (so I can't use didSelectRow).
I need to get parent ViewController from action when buttons are pressed. So I add this function to my custom cell class:
#IBAction func editBtnPressed (_ sender: Any) {
}
I need to get to self in order to add some subviews to it.
How can I access root ViewController as self?

I guess you should do it by creating property of your controller in cell class and assign property value when after creating cell in cellForRowAtIndexPath method.
For example:
in cell class
weak var yourController : YourViewController?
in cellForRowAtIndexPath
cell.yourController = self
then you can access the YourViewController in editBtnPressed action.
But i suggest you to do by creating button action programmatically in your controller class. that's the good approach.
for example:
class YourCellClass: UITableViewCell {
#IBOulet var myBtn: UIbutton!
...
}
Yourcontroller class
in cellForRowAtIndexPath
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as! YourCellClass
cell.myButton.addTarget(self, action: #selector(self. editBtnPressed), for: .touchUpInside)
and in editBtnPressed
func editBtnPressed (_ sender: Any) {
// you can access controller here by using self
}

Related

ios - How to get label value from table cell by click button in table cell?

I have an UITableView, in each cell it's have some label and a button. I want to get all label value when I click the button. How to do this? Thank you.
You can do it by closure or delegation
1: Closure
In your tableViewCell class create a variable like this
customObject is the object you passed the tableviewCell to load the data
var cellData: customObject? {
didSet {
// do your loding labels in here
}
}
var clickHandler: ((customObject) -> Void)!
and inside of you action button add this
#IBAction func replyAction(_ sender: Any) {
if let customObject = customObject {
clickHandler(customObject)
}
}
now go to where are you deque the table
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! YourCustomCell
// add this
cell.clickHandler = { customObject in
print("myCell.customObject = \(customObject)")
}
}
this will do the magic
2. Delegation
Create a delegate methode like this
protocol CustomCellDelegate {
func getCustomObject(in cell: CustomCell, withCustomObject object: CustomObject)
}
now in your cell class add delegate variable
var delegate: CustomCellDelegate?
and inside of you action button add this
#IBAction func replyAction(_ sender: Any) {
if let customObject = customObject {
delegate.getCustomObject(in: self, withCustomObject: customObject)
}
}
and now for the last part go to class you implemented the table view and this to where it shows
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! YourCustomCell
// add this
cell.delegate = self
}
and inside of class you should add you delegate method
extension YourClass: CustomCellDelegate {
getCustomObject(in cell: CustomCell, withCustomObject object: CustomObject) {
print("current cell data = \(CustomObject)"
}
}
this will do the job too
Hop this will Helps
Create IBAction method for the button inside the cell custom class and inside it print
print("label text : \(self.lbl.text)")
Or use delegate to send that value to the VC the contains the tableView
First of all. You should have a model object which you are using it to load the values of label. Use
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {}
to get the index then try getting the value from the model using the index
for example you have an array myArray then you could access the value using myArray[indexPath.row] to get the value. Then save, pass and use it where ever you want. Then implement a delegate method in your custom table cell class passing the indexPath. Then refresh the cell using tableView.reloadRows(at: [IndexPath(item:0,section:0)], with: .fade)

How to perform action for a custom UITableViewCell inside my viewController?

I have a custom cell that has a xib, this cell contains a button, when the button is pressed I want to do an action , but not inside my custom cell class but from inside the viewcontroller that contains the tableview of the custom cell, any help please?
First of all you should write a protocol for example:
protocol CustomCellDelegate {
func doAnyAction(cell:CustomUITableViewCell)
}
Then inside your custom cell class declare:
weak var delegate:CustomCellDelegate?
and inside your IBAction in the custom cell class:
#IBAction func onButtonTapped(_ sender: UIButton) {
delegate?.doAnyAction(cell: self)
//here we say that the responsible class for this action is the one that implements this delegate and we pass the custom cell to it.
}
Now in your viewController:
1- Make your view controller implement CustomCellDelegate.
2- In your cellForRow when declaring the cell don't forget to write:
cell.delegate = self
3- Finally call the function in your viewcontroller:
func doAnyAction(cell: CustomUITableViewCell) {
let row = cell.indexPath(for: cell)?.row
//do whatever you want
}
}
You can use delegate pattern. Create a custom protocol.
protocol CustomTableViewCellDelegate {
func buttonTapped() }
and on tableview cell conform delegate
class CustomTableViewCell: UITableViewCell {
var delegate: CustomTableViewCellDelegate!
#IBAction func buttonTapped(_ sender: UIButton) {
delegate.buttonTapped()
} }
table view data source
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellIdentifier", for: indexPath) as! CustomTableViewCell
cell.delegate = self
return cell
}
conform protocol(delegate) from table view controller or view controller
extension TestViewController: CustomTableViewCellDelegate {
func buttonTapped() {
print("do something...")
} }
Just get the UIButton in your cellForRowAtIndexpath.
Then write the following code.
button.addTarget(self, action:#selector(buttonAction(_:)), for: .touchUpInside).
func buttonAction(sender: UIButton){
//...
}
Add action for button from tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) method from your viewController.
cell.btn.tag = indexPath.row;
cell.btn.addTarget(self, action:#selector(handleButtonClicked(_:)), for: .touchUpInside).
Write selector function in your view Controller:
func handleButtonClicked(sender: UIButton){
int cellofClickedbutton = sender.tag;
//...
}
In your cell define a protocol:
protocol MyCellDelegate: class {
func buttonTapped()
}
and define a delegate variable:
weak var delegate: MyCellDelegate?
Make your viewController conform to the defined protocol.
When creating the cell in the viewController, assign it the delegate:
cell.delegate = self
When the button in the cell is tapped, send the delegate appropriate message:
delegate?.buttonTapped()
In your viewController implement the method:
func buttonTapped() {
//do whatever you need
}
You can pass as an argument the cell's index, so the viewController knows which cell button was tapped.

How to present view controller from UILabel with tap gesture recognized in custom table view cell?

I have a custom dynamic table view cell with a label that has a tap gesture recognized added. When the user taps the label, not anywhere else in the cell, I want to present a view controller.
The instagram app has this feature. Ie. when you tap likes, it takes you to a likes table view, when you tap comments, it shows you to a comments table view. This is the same experience I want.
I am not looking to use didSelectRow because then it kind of defeats the purpose of having the specific target area to tap to show a new view controller.
So, how can I present a view controller from a tap gesture recognizer in a subclass of UITableViewCell?
UPDATED:
I am passing a closure to my custom TableViewCell which is successfully being called when the button is pressed. But I am stuck in the TableView and cannot pass information to the next View Controller I want to present. And I can't actaully perform the segue either :\
// From UITableView
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let story = stories[indexPath.row]
if let cell = tableView.dequeueReusableCell(withIdentifier: "Fun Cell", for: indexPath) as? FunTableViewCell {
cell.configureCell(title: story.title, info: story.info)
cell.buttonAction = { [weak self] (cell) in
print("the button was pressed for \(story.title)")
self?.buttonWAsTapped(title: story.title)
}
return cell
} else {
return FunTableViewCell()
}
}
func buttonWAsTapped(title: String) {
// Need to pass something to the next View Controller... but how???
if let nextVC = UIViewController() as? DetailViewController {
nextVC.storyTitle = title
performSegue(withIdentifier: "Button Pressed", sender: self)
}
}
// Custom TableViewCell
class FunTableViewCell: UITableViewCell {
#IBOutlet weak var funLabel: FunLabel!
#IBOutlet weak var standardLabel: TappedLabel!
#IBOutlet weak var funButton: FunButton!
var buttonAction: ((UITableViewCell) -> Void)?
override func awakeFromNib() {
super.awakeFromNib()
let tap = UITapGestureRecognizer(target: self, action: #selector(readMoreTapped))
tap.numberOfTapsRequired = 1
standardLabel.addGestureRecognizer(tap)
standardLabel.isUserInteractionEnabled = true
}
#IBAction func btnPressed(sender: UIButton) {
print("Button pressed")
buttonAction?(self)
}
When creating the cell, pass a block to it. That's the handler for the button.
When the button tapped, call the block.
You can save the block as a property of the subclass of UITableViewCell.
In your tableViewCell class, add a property:
class CustomTableViewCell: UITableViewCell {
open var completionHandler: (()->Void)?
}
In your viewController that has the tableView:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = CustomTableViewCell()
cell.completionHandler = {
() -> Void in
let newViewController = UIViewController()
//configure the VC here base on the indexPath
self.present(newViewController, animated: true, completion: nil)
}
return cell
}

Detect UIButton from Custom UITableViewCell in Swift 3 [duplicate]

This question already has answers here:
Correct way to setting a tag to all cells in TableView
(3 answers)
Closed 6 years ago.
I have a custom UITableViewCell in which I have connected my UIButton using Interface Builder
#IBOutlet var myButton: UIButton!
Under cell configuration of UITableViewController, I have the following code:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var customCell = tableView.dequeueReusableCell(withIdentifier: self.MY_CELL_IDENTIFIER, for: indexPath) as! myCustomCell
// CONFIGURE OTHER CELL PARAMETERS
customCell.myButton.tag = indexPath.row
customCell.myButton.addTarget(self, action: #selector(myButtonPressed), for: UIControlEvents.touchUpInside)
return customCell
}
Finally, I have
private func myButtonPressed(sender: UIButton!) {
let row = sender.tag
print("Button Sender row: \(row)")
}
This code is not working, unless I change the function definition to below:
#objc private func myButtonPressed(sender: UIButton!) {
let row = sender.tag
print("Button Sender row: \(row)")
}
Is there a better way to implement UIButton on custom UITableViewCell in Swift 3
I am not a big fan using view tags. Instead of this, you could use the delegate pattern for your viewController to be notified when a button has been hit.
protocol CustomCellDelegate: class {
func customCell(_ cell: UITableViewCell, didPressButton: UIButton)
}
class CustomCell: UITableViewCell {
// Create a delegate instance
weak var delegate: CustomCellDelegate?
#IBAction func handleButtonPress(sender: UIButton) {
self.delegate?.customCell(self, didPressButton: sender)
}
}
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var customCell = tableView.dequeueReusableCell(withIdentifier: "identifier", for: indexPath) as! CustomCell
// CONFIGURE OTHER CELL PARAMETERS
//Assign the cell's delegate to yourself
customCell.delegate = self
return customCell
}
}
extension ViewController: CustomCellDelegate {
// You get notified by the cell instance and the button when it was pressed
func customCell(_ cell: UITableViewCell, didPressButton: UIButton) {
// Get the indexPath
let indexPath = tableView.indexPath(for: cell)
}
}
Yes, there is a smarter and better way to do this. The main problem of your method is that it only work if no insert, delete or move cells operation occurs. Because anyone of these operations can change de indexPath of the cells that were created for a different indexPath.
The system I use is this:
1.- Create a IBAction in your cell class MyCustomCell (With uppercase M. It is a class, so name it properly).
2.- Connect the button to that action.
3.- Declare a protocol MyCustomCellDelegate with, at least, a method
func myCustomCellButtonAction(_ cell:MyCustomCell)
and add a property to MyCustomCell
var delegate : MyCustomCellDelegate?
4.- Set the view controller as MyCustomCellDelegate
In the method of MyCustomCell connected to the button invoke the delegate method:
delegate?.myCustomCellButtonAction( self )
5.- In the view controller, in the method cellForRowAt:
customCell.delegate = self
6.- In the view controller, in the method myCustomCellButtonAction:
func myCustomCellButtonAction( _ cell: MyCustomCell ) {
let indexPath = self.tableView.indexPathForCell( cell )
// ...... continue .....
}
You can also use delegates to do the same.
Directly map the IBAction of button in your custom UITableViewCell Class.
Implement the delegate methods in viewcontroller and On button action call the delegate method from custom cell.

Changing button in cell when pressed

I'm trying to change the color of a button when pressed. Currently its inside a table view cell.
The way I'm doing it is adding as so:
#IBAction func upVote(sender: AnyObject) {
sender.setImage(UIImage(named: "bUpVote"), forState: .Normal)
}
and this is done inside the cell class (not the view controller class).
It works, but the change also applies to every third cell that follows it for the rest of the table.
Any work around? Thanks!
There are many way to solve this issue, one of the method is as follows
Add this to your customCell class,
#objc protocol MyTableViewCellDelegate {
func controller(controller: MyTableViewCell, button: UIButton, selectedButtonIndexPath : NSIndexPath)
}
class MyTableViewCell: UITableViewCell {
var delegate: AnyObject?
var indexPath : NSIndexPath?
#IBOutlet weak var button: UIButton!//outlet of button
button Action
#IBAction func buttonAction(sender: UIButton)//IF the sender type is AnyObject, you have to change it as UIButton
{
self.delegate?.controller(self, button: sender, selectedButtonIndexPath: indexPath!)
}
Add this to your ViewController class that has UITableView
class MyTableViewController: UITableViewController, MyTableViewCellDelegate { // I created a subClass of UITableViewController, your's may be different
var arraySelectedButtonIndex : NSMutableArray = []//global declaration
Since i created my custom cell using xib, in viewDidLoad()
tableView.registerNib(UINib(nibName: "MyTableViewCell", bundle: nil), forCellReuseIdentifier: "CustomCell")//Since, I use custom cell in xib
define delegate of custom cell by adding this
func controller(controller: MyTableViewCell, button: UIButton, selectedButtonIndexPath : NSIndexPath)
{
if(arraySelectedButtonIndex .containsObject(selectedButtonIndexPath)==false)
{
arraySelectedButtonIndex.addObject(selectedButtonIndexPath)
button.setImage(UIImage(named: "bUpVote") , forState: .Normal)
}
else
{
arraySelectedButtonIndex.removeObject(selectedButtonIndexPath)//If you need to set Deselect image
button.setImage(UIImage(named: "deselectImage") , forState: .Normal)//If you need to set Deselect image
}
}
In tableView dataSource (cellForRowAtIndexPath)
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("CustomCell", forIndexPath: indexPath) as! MyTableViewCell
cell.delegate = self
cell.indexPath = indexPath
if(arraySelectedButtonIndex .containsObject(indexPath))
{
cell.button.setImage(UIImage(named: "bUpVote"), forState: .Normal)
}
else
{
cell.button.setImage(UIImage(named: "deselectImage"), forState: .Normal)//If you need to set Deselect image
}
return cell
}
It's because cells are reused by the tableView. if you need to persist the state of subviews in the cell, you need to update your data source and reflect the changes in cellForRowAtIndexPath method.
This is not the way to do it. You store the state of the button in your model. Eg: say store the item's upvoted status in your model :
class Post
{
var title : String
var upvoted : Bool
}
How to get the index path ?
Move the IBAction method on your custom tableview subclass. Add a property called delegate to the cell and set it to your controller in cellForRowAtIndexPath: . Now in the action method inform the delegate.
I have described this in detail here : https://stackoverflow.com/a/32250043/1616513
Now when the user upvotes you update the model :
#IBAction func upVotedInCell(sender: UITableViewCell) {
var indexPath = self.tableView.indexPathForCell(sender)
self.items[indexPath].upvoted = true
self.tableView.reloadRowsAtIndexPaths([indexPath],UITableViewRowAnimation.None)
}

Resources