This is driving me nuts, I've been reading SO for hours and tried everything and cant get this button selector to work. This shouldn't be difficult.
Inside CellForItemAt i have set the button tag and try call the button.
cell.deleteCellButton.tag = indexPath.item
cell.deleteCellButton.addTarget(self, action: #selector(deleteCellButtonTapped(sender:)), for: UIControlEvents.touchUpInside)
I've tried (_:), "deleteCellButtonTapped:", and any other number of parenthesis combinations and i still get unrecognised selector. i don't know why autocomplete recommends (sender:) I've never seen this before.
then my button function:
func deleteCellButtonTapped(sender: UIButton!) {
self.packArray.remove(at: sender.tag)
print(packArray.count)
self.outerCollectionView.deleteItems(at: [IndexPath(item: sender.tag, section: 0)])
self.outerCollectionView.reloadData()
self.outerCollectionView.layoutIfNeeded()
}
Assuming you're using Swift 3, and func deleteCellButtonTapped(sender: UIButton!) is in the same class:
addTarget(self, action: #selector(deleteCellButtonTapped(sender:)), for: .touchUpInside)
works fine.
Refering the selector method from it's class works for me.
What you can do is access selector method prefixing by it's class name.
I assume your class name is MyClassViewController. And you code will look like:
class MyClassViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
.... // Other implementation methods
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
....// create cell object and dequeue
cell.deleteCellButton.addTarget(self, action: #selector(MyClassViewController.deleteCellButtonTapped(_:)), for: .touchUpInside)
return cell
}
func deleteCellButtonTapped(_ sender: Any) {
// your method implementation
print("Selector method called")
}
}
Hope this works fine
Related
I have a collection view and image view inside it and I added a UIButton to delete the image after selection. When I click the button it crashes and gives me this error:
AdPostViewController deleteUser]: unrecognized selector sent to instance 0x7fb588d5b7f0
Why is this happening and how do I fix this? Here is my code:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImageCell", for: indexPath) as! ImageCell
let img = self.PhotoArray[indexPath.row]
cell.image.image = img
cell.deleteButton?.layer.setValue(indexPath.row, forKey: "index")
cell.deleteButton?.addTarget(self, action: Selector(("deleteUser")), for: UIControl.Event.touchUpInside)
return cell
}
func deleteUser(_ sender: UIButton) {
let i: Int = (sender.layer.value(forKey: "index")) as! Int
PhotoArray.remove(at: i)
// PhotoArray.removeAtIndex(i)
ImagesCollectionView.reloadData()
}
One problem is that you are forcing manual formation of the Objective-C selector, and you don't actually know how to form an Objective-C selector manually so you are getting it wrong. Don't do that! Let the compiler form the selector for you. That's its job. Replace
action: Selector(("deleteUser"))
with
action: #selector(deleteUser)
Also, you need to expose your deleteUser method to Objective-C explicitly:
#objc func deleteUser(_ sender: UIButton) {
Otherwise Objective-C still won't be able to introspect your class and find this method when the time comes to call it. Fortunately, when you switch to #selector syntax, the compiler will call out that issue for you!
I have a collection view where each of the cells has a delete button. I added the following code to cellForItemAt indexPath function.
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellTwo", for: indexPath) as! CustomCellTwo
cell.deleteButton.layer.setValue(indexPath.row, forKey: "index")
cell.deleteButton.addTarget(self, action: #selector(deleteCell), for: .touchUpInside)
Initially it looked as if it was working great. However, I found out that the add target function does not get called at the first tap if I scroll back and forth and then tap the delete button. If I tap again, it works as expected. Only the first tap does not work.
I have been trying to find a reason and a solution for several hours... Please help provide any ideas and advice.
Try to move buttons handling into CustomCellTwo implementation. Handle button event touchUpInside with #IBAction func. Now you can debug it with breakpoint set in this function's body.
Also add closure type variable to your CustomCellTwo to pass deleteCell calls into it. So it could also be checked with breakpoint.
Example:
class CustomCellTwo: UICollectionViewCell {
var onDelete: (() -> Void)?
#IBAction func onDeleteButtonTouch(_ sender: Any) {
onDelete?()
}
}
// in your UICollectionViewDataSource
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellTwo", for: indexPath) as! CustomCellTwo
cell.onDelete = {
self.deleteCell(indexPath)
}
}
So I've been searching StackExchange on how to put a UIButton inside of a UITableViewCell and thought I found the answer, but keep getting an "unrecognized selector sent to instance" error.
Here's where I call the function
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tblTeams.dequeueReusableCell(withIdentifier: "cellReuse")! as! TeamsTableViewCell
cell.btnLeave.tag = indexPath.row
cell.btnLeave.addTarget(self, action: "LeaveTeam:", for: UIControlEvents.touchUpInside)
return cell
}
and here's where the function is. It's within the same class, and even extension, as the prior code block
#IBAction func LeaveTeam(sender: UIButton){
}
I've tried rewording the quote, I've tried using #selector... just please tell me how I do it right. Thanks!
Replace
cell.btnLeave.addTarget(self, action: #selector(leaveTeam(_:)) for: UIControlEvents.touchUpInside)
#objc func leaveTeam(_ sender: UIButton) {---}
start method name in lower case
addTarget(self, action:#selector(leaveTeam(sender:)), for: .touchUpInside)
#objc func leaveTeam(sender : UIButton) -> Void {
}
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()
}
}
I have a custom button in my collection view cell. I just want to pass indexPath to it but I am getting
"unrecognized selector error"
Here is my code
cell.showMapButton.addTarget(self, action: #selector(testFunc(indexPath:)), for: .touchUpInside)
and the function is
func testFunc(indexPath: IndexPath){
print("Testing indexPath \(indexPath)")
}
If I remove the indexPath argument it works fine and the function gets called but I need that argument so please help me in resolving this issue.
In the addTarget(:action:for:) method for UIButton, the action can at most accept a single UIButton or any of it's superclass as parameter. If you need the indexPath of your button, you need to make it a property of your UIButton by subclass or other means. My way of doing it is to create a subclass of UIButton that have indexPath as it's property:
class ButtonWithIndexPath: UIButton {
var indexPath:IndexPath?
}
Then add target as normal:
cell.showMapButton.addTarget(self, action: #selector(testFunc(button:)), for: .touchUpInside)
Not forgetting to set the indexPath of your button to that of which ever cell it is in
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "myCell", for: indexPath) as! myCell
cell.button.indexPath = indexPath
...
return cell
}
And cast it into it's custom subclass in the function to read the indexPath:
func textFunc(button: UIButton) {
let currentButton = (button as! ButtonWithIndexPath)
print(currentButton.indexPath)
}
You can pass UIButton instance with target selector parameters for button action.
try with following code:
Add/replace below code, belongs to collection view cell into your collection view data source method - cellForRowAtIndexPath
cell.showMapButton.tag = indexPath.row
cell.showMapButton.addTarget(self, action: #selector(testFunc(button:)), for: .touchUpInside)
For Swift 4 - define your selector function using #objc, like below.
#objc func testFunc(button: UIBUtton){
print("Index = \(button.tag)")
}