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()
}
}
Related
I have a custom UICollectionViewCell with a button inside it. When I tap the button, an event is fired inside that subclass. I want to then trigger an event on the UICollectionView itself, which I can handle in my view controller.
Pseudo-code:
class MyCell : UICollectionViewCell {
#IBAction func myButton_touchUpInside(_ sender: UIButton) {
// Do stuff, then propagate an event to the UICollectionView
Event.fire("cellUpdated")
}
}
class MyViewController : UIViewController {
#IBAction func collectionView_cellUpdated(_ sender: UICollectionView) {
// Update stuff in the view controller
// to reflect changes made in the collection view
}
}
Ideally, the event I define would appear alongside the default action outlets in the Interface Builder, allowing me to then drag it into my view controller code to create the above collectionView_cellUpdated function, similar to how #IBInspectable works in exposing custom properties.
Is there any way to implement a pattern like this in native Swift 3? Or if not, any libraries that make it possible?
I don't understand your question completely but from what I got, you can simply use a closure to pass the UIButton tap event back to the UIViewController.
Example:
1. Custom UICollectionViewCell
class MyCell: UICollectionViewCell
{
var tapHandler: (()->())?
#IBAction func myButton_touchUpInside(_ sender: UIButton)
{
tapHandler?()
}
}
2. MyViewController
class MyViewController: UIViewController, UICollectionViewDataSource
{
//YOUR CODE..
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! MyCell
cell.tapHandler = {
//Here you can do your custom handling
}
return cell
}
}
Let me know if you face any issues.
Best thing to do is to make a custom protocol for your custom cell class
protocol CustomCellProtocolDelegate {
func custom(cell: YourCellClass, hadButton: UIButton, pressedWithInfo : [String:Any]?)
}
Make this cell class have this protocol as a peculiar delegate, and to trigger this delegate:
class YourCellClass: UICollectionViewCell {
var delegate : CustomCellProtocolDelegate?
var indexPath : IndexPath? //Good practice here to have an indexPath parameter
var yourButton = UIButton()
init(frame: CGRect) {
super.init(frame: frame)
yourButton.addTarget(self, selector: #selector(triggerButton(sender:)))
}
func triggerButton(sender: UIButton) {
if let d = self.delegate {
d.custom(cell: self, hadButton: sender, pressedWithInfo : /*Add info if you want*/)
}
}
}
In your controller, you conform it to the delegate, and you apply the delegate to each cell in cellForItem: atIndexPath:
class YourControllerThatHasTheCollectionView : UIViewController, CustomCellProtocolDelegate {
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "identifier", for: indexPath) as! YourCellClass
cell.delegate = self
cell.indexPath = indexPath
return cell
}
func custom(cell: YourCellClass, hadButton: UIButton, pressedWithInfo : [String:Any]?) {
//Here you can process which button was selected, etc.. and apply your changes to your collectionview
}
}
Best practice is to pass the cell's indexPath parameter in the delegate method inside of pressedWithInfo. It saves you the trouble of calculating which cell actually was pressed; hence why i usually add an indexPath element to each of my UICollectionViewCell subclasses. Better yet, include the index inside the protocol method:
protocol CustomCellProtocolDelegate {
func custom(cell: YourCellClass, hadButton: UIButton, pressedAt: IndexPath, withInfo : [String:Any]?)
}
func triggerButton(sender: UIButton) {
if let d = self.delegate {
d.custom(cell: self, hadButton: sender, pressedAt: indexPath!, withInfo : /*Add info if you want*/)
}
}
I have an extension of UICollectionViewCell class. When something is pressed in this cell, I am trying to notify the Controller. I am not quite sure if the protocol delegate pattern is the way to go about it. I am not sure how to use it in this case. I have the following class outside my extension of UICollectionViewCell class.
protocol bundleThreadsDelegate: class {
func bundleThreadsDidSelect(_ viewController: UIViewController)
}
And I have the following property:
public weak var delegate: bundleThreadsDelegate? in my extension.
I am not quite sure where to go on from Here. Please help.
You said "when something is pressed in this cell", not when the cell itself is pressed, so I assume you may want multiple actionable items in your cell. If that's what you really mean then in your UICollectionViewCell, you could simply add a UIButton (no need for delegates as mentioned here because everything can happen within the same view controller—use delegates when communicating between different objects):
class MyCollectionViewCell: UICollectionViewCell {
let someButton = UIButton()
...
}
When you create the UICollectionView, to make it easiest, set the view controller it's in as the data source:
let myCollection = UICollectionView(frame: .zero, collectionViewLayout: MyCollectionViewFlowLayout())
myCollection.dataSource = self
...
Then in your data source, which would be in something like MyViewController, give the button a target:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let path = indexPath.item
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "myCell", for: indexPath) as! MyCollectionViewCell
cell.someButton.addTarget(self, action: #selector(someButtonAction(_:)), for: .touchUpInside)
return cell
}
And make sure that the action method is also in MyViewController along with the data source.
#objc func someButtonAction(_ sender: UIButton) {
print("My collection view cell was tapped")
}
Now you can have multiple buttons within one collection view cell that do different things. You can also pass in arguments from the cell to the button action for further customization.
However, if you want action when the entire cell is pressed, use the delegate method already mentioned (or make the entire cell a UIButton which is not as elegant but that's open to interpretation).
Use this CollectionView Delegate method to notify the ViewController when your cell is selected by a user.
func collectionView(_ collectionView: UICollectionView,
didSelectItemAt indexPath: IndexPath)
Chris is correct, assuming you're only interested in the whole cell being selected. If you have a button or something within your cell and it's that press event you're interested in then yeah, you could use a delegate.
As an aside, protocols usually start with an uppercase letter, i.e. BundleThreadsDelegate rather than bundleThreadsDelegate, but that's up to you. Your general approach could be something like this (note this is pseudo code):
protocol YourProtocol {
func didPressYourButton()
}
class YourCell {
#IBOutlet weak var yourButton: UIButton!
public weak var yourButtonDelegate: YourProtocol?
func awakeFromNib() {
super.awakeFromNib()
yourButton.addTarget(self, action: #selector(didPressYourButton), for: .touchUpInside)
}
func didPressYourButton() {
yourButtonDelegate?.didPressYourButton()
}
}
And then in your view controller's cellForRowAt function:
let cell = ...
cell.yourButtonDelegate = self
Then conform to the protocol and implement the method in your view controller:
extension YourViewController: YourProtocol {
func didPressYourButton() {
doAllTheThings()
}
}
In my iOS Switch app I have a BEMCheckBox in each table cell. When dequeuing a cell I want to set a delegate that gets called.
My problem is that the checkbox works fine but the delegate is not never called. How to add a delegate to each checkbox?
I want to know which indexPath for checkbox. The plan is to pass model object to the delegate and update it accordingly.
Table cell
let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath)
cell.doneCheckbox.delegate = DoneBEMCheckBoxDelegate()
return cell
Delegate is very simple
class DoneBEMCheckBoxDelegate: NSObject, BEMCheckBoxDelegate {
#objc func didTap(_ checkBox: BEMCheckBox) {
print("Checkbox tapped")
}
}
cell.doneCheckbox.delegate = DoneBEMCheckBoxDelegate() is creating a new DoneBEMCheckBoxDelegate object in a local variable and assigning that as the delegate. Since the delegate property is weak, it will be released as soon as the function exits because there is no strong reference remaining.
I would suggest that having a separate object class to be the delegate probably isn't what you want anyway.
I would set the cell to be the check box delegate and then declare another protocol so that the cell can have its own delegate, which would be your table view controller.
protocol MyCellDelegate {
func checkBox(for cell: MyCell, isOn: Bool)
}
class MyCell: UITableViewCell, DoneBEMCheckBoxDelegate {
var delegate: MyCellDelegate?
override func awakeFromNib() {
super.awakeFromNib()
self.doneCheckBox.delegate = self
}
#objc func didTap(_ checkBox: BEMCheckBox) {
print("Checkbox tapped")
self.delegate?.checkBox(for: self, isOn: checkBox.isOn)
}
}
class YourViewController: MyCellDelegate {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
...
cell.delegate = self
return cell
}
func checkBox(for cell: MyCell, isOn: Bool) {
guard let indexPath = tableView.indexPath(for: cell) else {
return
}
// Now do whatever you need to with indexPath
}
}
This way you avoid creating additional objects and datastructures and you won't have a problem if cells are re-ordered as there is no dependency on index path.
I noticed that delegate is a weak reference in checkbox class, as it is supposed to be :) So my delegate was freed after method scope ended.
I fixed this by storing delegates in view controller during their usage.
var checkboxDelegates: [IndexPath:DoneBEMCheckBoxDelegate] = [:]
...
let checkboxDelegate = DoneBEMCheckBoxDelegate(realm: realm, set: set)
checkboxDelegates[indexPath] = checkboxDelegate
cell.doneCheckbox.delegate = checkboxDelegate
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.
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.