I want to extend UIButton class to make my own radio button.
class UIRadioButton : UIButton {
var checked = CheckedEnum.unchecked
enum CheckedEnum{
case checked , unchecked
}
}
It's inner class in Viewcontroller. But when I want to make this button sends the action to view Controller, as I usually do, it doesn't work. It's my button connections window:
enter image description here
And it's usually button connections window:
enter image description here
In my opinion, you should implement a delegate of your UITableViewCell and call its method when button receives touch event. Example:
protocol CellButtonDelegate: class {
func cellButton(cell: UITableViewCell, didTouch button: UIButton)
}
class Cell: UITableViewCell {
weak var delegate: CellButtonDelegate?
#IBAction func buttonTouched(sender: UIButton) {
delegate?.cellButton(cell: self, didTouch button: sender)
}
}
Then you make your view controller a delegate of each table view cell and handle this event.
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView. dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! Cell
// configure cell
cell.delegate = self
}
}
extension ViewController: CellButtonDelegate {
func cellButton(cell: UITableViewCell, didTouch button: UIButton) {
// handle button touch event, save indexPath of the cell to alter representation of table view after reloading data etc.
}
}
Hope this will help.
Related
Hello the image above is the UI of my todo list app, now I just want to show the detail of item (First Item, second Item etc) when I click the detail button in the tableviewcell. So in order to get the property of the item, I need to know the indexPath of the row that I just clicked on the detail button.
I have tried some properties of the tableview like didSelectRowAt, or indexPathForSelectedRow, but both not work. For didSelectRowAt user need to click on the row first then click the detail button, and that's not what I want, and the indexPathForSelectedRow is not working for me.
A common, generalized solution for this type of problem is to connect the #IBAction of the button to a handler in the cell (not in the view controller), and then use a delegate-protocol pattern so the cell can tell the table when the button was tapped. The key is that when the cell does this, it will supply a reference to itself, which the view controller can then use to determine the appropriate indexPath (and thus the row).
For example:
Give your UITableViewCell subclass a protocol:
protocol CustomCellDelegate: class {
func cell(_ cell: CustomCell, didTap button: UIButton)
}
Hook up the #IBAction to the cell (not the view controller) and have that call the delegate method:
class CustomCell: UITableViewCell {
weak var delegate: CustomCellDelegate?
#IBOutlet weak var customLabel: UILabel!
func configure(text: String, delegate: CustomCellDelegate) {
customLabel.text = text
self.delegate = delegate
}
#IBAction func didTapButton(_ button: UIButton) {
delegate?.cell(self, didTap: button)
}
}
Obviously, when the cell is created, call the configure method, passing, amongst other things, a reference to itself as the delegate:
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { ... }
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
let text = ...
cell.configure(text: text, delegate: self)
return cell
}
}
Finally, have the delegate method call indexPath(for:) to determine the index path for the cell in question:
extension ViewController: CustomCellDelegate {
func cell(_ cell: CustomCell, didTap button: UIButton) {
guard let indexPath = tableView.indexPath(for: cell) else { return }
// use `indexPath.row` here
}
}
The other approach is to use closures, but again using the same general pattern of hooking the button #IBAction to the cell, but have it call a closure instead of the delegate method:
Define custom cell with closure that will be called when the button is tapped:
class CustomCell: UITableViewCell {
typealias ButtonHandler = (CustomCell) -> Void
var buttonHandler: ButtonHandler?
#IBOutlet weak var customLabel: UILabel!
func configure(text: String, buttonHandler: #escaping ButtonHandler) {
customLabel.text = text
self.buttonHandler = buttonHandler
}
#IBAction func didTapButton(_ button: UIButton) {
buttonHandler?(self)
}
}
When the table view data source creates the cell, supply a handler closure:
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { ... }
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
let text = ...
cell.configure(text: text, buttonHandler: { [weak self] cell in // the `[weak self]` is only needed if this closure references `self` somewhere
guard let indexPath = tableView.indexPath(for: cell) else { return }
// use `indexPath` here
})
return cell
}
}
I personally prefer the delegate-protocol pattern, as it tends to scale more nicely, but both approaches work.
Note, in both examples, I studiously avoided saving the indexPath in the cell, itself (or worse, “tag” values). By doing this, it protects you from getting misaligned if rows are later inserted and deleted from the table.
By the way, I used fairly generic method/closure names. In a real app, you might give them more meaningful names, e.g., didTapInfoButton, didTapSaveButton, etc.) that clarifies the functional intent.
Implement the delegate method tableView(_:accessoryButtonTappedForRowWith:)
func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath)
However if you want to navigate to a different controller connect a segue to the accessory view button
If the button is a custom button see my answer in Issue Detecting Button cellForRowAt
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.
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
}
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.
I use a main class call newsFeedCointroller as UICollectionViewController.
1. In cell inside I have a newsfeed with a like button (to populate the cell I use a class called "FeedCell")
2. Out from cells (in mainview) I have a label (labelX) used for "splash message" with a function called "messageAnimated"
How can I call the "messageAnimated" function from the button inside the cells.
I want to to change the label text to for example: "you just liked it"...
Thanks for helping me
In your FeedCell you should declare a delegate (Read about delegate pattern here)
protocol FeedCellDelegate {
func didClickButtonLikeInFeedCell(cell: FeedCell)
}
In your cell implementation (suppose that you add target manually)
var delegate: FeedCellDelegate?
override func awakeFromNib() {
self.likeButton.addTarget(self, action: #selector(FeedCell.onClickButtonLike(_:)), forControlEvents: .TouchUpInside)
}
func onClickButtonLike(sender: UIButton) {
self.delegate?.didClickButtonLikeInFeedCell(self)
}
In your View controller
extension FeedViewController: UICollectionViewDataSource, UICollectionViewDelegate {
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("feedCell", forIndexPath: indexPath) as! FeedCell
// Do your setup.
// ...
// Then here, set the delegate
cell.delegate = self
return cell
}
// I don't care about other delegate functions, it's up to you.
}
extension FeedViewController: FeedCellDelegate {
func didClickButtonLikeInFeedCell(cell: FeedCell) {
// Do whatever you want to do when click the like button.
let indexPath = collectionView.indexPathForCell(cell)
print("Button like clicked from cell with indexPath \(indexPath)")
messageAnimated()
}
}