I have the custom CollectionViewCell and when a user clicks on a button which is located in the collectionviewcell then delegate method is getting called. It works, but I am passing UICollectionViewCell as a param. However, it is not efficient.
I would like to pass only selected customviewcell's index rather than entire Cell Object. How would you achieve that?
protocol CustomCollectionViewCellDelegate: AnyObject {
func greenButtonTapped(cell: UICollectionViewCell)
}
class CustomCollectionViewCell : UICollectionViewCell {
weak var delegate: CustomCollectionViewCellDelegate?
#IBAction func greenButtonTapped(_ sender: Any) {
delegate?.greenButtonTapped(cell: self)
}
}
or something like that as a closure ?
var onTappingGreenButton: (UIButton, IndexPath) -> ()
Related
I'm trying to make a button do something in another view controller. I made a delegate, added a function with a requirement of having an indexPath so I could use it to delete items in an array, but when I make a variable indexPath equal to IndexPath.self and try feeding it into the actual IBAction, it spits out the error shown in the title. Here is my code for my first view controller, where the IBAction and protocol is defined.
import UIKit
protocol ToDoItemCellDelegate: AnyObject {
func didTapX(with indexPath: IndexPath)
}
class ToDoItemCell: UITableViewCell {
weak var delegate: ToDoItemCellDelegate?
static let identifier = "ToDoItemCell"
static func nib() -> UINib {
return UINib(nibName: "ToDoItemCell", bundle: nil)
}
#IBOutlet var xButton: UIButton!
private var indexPath = IndexPath.self
#IBAction func didTapX(_ sender: UIButton) {
delegate?.didTapX(with: indexPath)
}
override func awakeFromNib() {
super.awakeFromNib()
}
}
You have mistaken type and entity. Your delegate expects an entity and your var only provides a type. You stated in your comment that you do not need to provide any meaningfull value here so you have multiple possibilities:
Remove the argument from the delegate:
protocol ToDoItemCellDelegate: AnyObject {
func didTapX()
}
or create a default value while calling the delegate and remove the property:
#IBAction func didTapX(_ sender: UIButton) {
delegate?.didTapX(with: IndexPath(item: 0, section: 0))
}
or assign a defalut to the property and use it while calling the delegate:
private var indexPath = IndexPath(item: 0, section: 0)
How to delete a row from tableview using custom buttom
//CustomCell.swift
protocol FavoriteCellDelegate {
func deleteButton(sender:CustomCell)
}
class FavoriteItemTableViewCell: UITableViewCell{
var delegate: FavoriteCellDelegate!
#IBAction func deleteButton(_ sender: UIButton) {
delegate.deleteButton(sender: self)
}
}
CustomClass:UITableViewDataSource,UITableViewDelegate,CustomCellDelegate{
#IBOutlet weak var tableView: UITableView!
// all necessary functions for table view....
// Function delegated to perform action.
func deleteButton(sender:FavoriteItemTableViewCell){
//How should I delete. How can I get index path here
}
}
Q. What should I write in the deleteButton function ? I am unable to get the indexPath here so what should I do instead. I already have another button in cell and the delegation is working fine.
you can get indexPath using table view point like this
let buttonPosition : CGPoint = sender.convert(sender.bounds.origin, to: tableview)
let indexPath = tableview.indexPathForRow(at: buttonPosition)
I have a UICollectionViewCell header on a UICollectionViewController, and I've added a button to it. I would like for the button, when clicked, to push a new view controller atop the current one. The problem is that the button doesn't have access to the navigation controller of the UICollectionViewController, so I there's no way to directly push a controller from, say, a connector to the buttn (that I know of). Is there any way to achieve this? Maybe something can be overriden, such as a collectionView function. Thanks!
If you just want to process the cell selection there is a handy method in the UICollectionViewDelegate that you can implement to get the index path of the pressed cell.
If your goal is to have a custom button inside the cell (or maybe even several) you can use delegation pattern to retrieve user actions to your controller to than process in any way, including pushing/presenting new controllers. Assign the controller's instance (the one managing the collection view) to the delegate member of your cell.
Define a protocol that I would call something like MyCustomCellDelegate (replace MyCustomCell with a more appropriate name for your case). Something like MyCustomCellDelegate: class { func didPressButtonX() }
Declare an optional delegate property in your cell subclass. weak var delegate: MyCustomCellDelegate?
Implement your delegate protocol by the class you want to respond to button presses (or any other interactions defined by your protocol).
Every time you create/dequeue a cell for your UICollectionView to use you set the delegate property to the view controller managing the collection view. cell.delegate = self (if done inside the view controller itself).
After receiving the UI event inside your custom cell use your delegate property to retrieve the action to the controller (or with ever object you used when assigning the property). Something like: delegate?.didPressButtonX()
In your class that implements MyCustomCellDelegate use the method to push the new controller.
Below I will provide sample code that should give more details on the implementation of the proposed solution:
// In your UICollectionViewCell subclass file
protocol MyCustomCellDelegate: class {
func didPressButtonX()
func didPressButtonY()
}
MyCustomCell: UICollectionViewCell {
weak var delegate: MyCustomCellDelegate?
#IBOutlet var buttonX: UIButton!
#IBOutlet var buttonY: UIButton!
#IBAction func didPressButtonX(sender: Any) {
delegate?.didPressButtonX()
}
#IBAction func didPressButtonY(sender: Any) {
delegate?.didPressButtonY()
}
}
// Now in your UICollectionViewController subclass file
MyCustomCollectionViewController: UICollectionViewController {
// ...
override func collectionView(UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier identifier: "YourCellIdentifierGoesHere", for indexPath: indexPath) as! MyCustomCell
// In here we assign the delegate member of the cell to make sure once
// an UI event occurs the cell will call methods implemented by our controller
cell.delegate = self
// further cell setup if needed ...
return cell
}
}
// In order for the instance of our controller to be used as cell's delegate
// we implement the protocol that we defined earlier in the cell file
extension MyCustomCollectionViewController: MyCustomCellDelegate {
func didPressButtonX() {
print("X button was pressed")
// now lets finally push some new controller
let yourNextCoolViewController = UIViewController()
self.push(yourNextCoolViewController, animated: true)
// OR if you are using segues
self.performSegue(withIdentifier: "YourSegueIdentifierGoesHere", sender: self)
}
func didPressButtonY() {
print("Y button was pressed")
}
}
How do I tell if I button in the collection view header is selected when populating my collection view? I have 2 tabs in the header which determine which data I populate the collection view with so I want to be able to switch the data and reload the collection view when the user selects a different tab.
some code from header class as requested...I don't see the point though it's just a button. I want to see if this button is selected while populating the cells and cell count etc.
class UserSearchHeader: UICollectionReusableView {
#IBOutlet weak var friendsButton: UIButton!
#IBOutlet weak var peopleButton: UIButton!
#IBOutlet weak var customSlider: UIView!
override func awakeFromNib() {
super.awakeFromNib()
self.friendsButton.selected = true
customSlider.layer.cornerRadius = customSlider.frame.height / 2
}
#IBAction func friendsButtonTapped(sender: AnyObject) {
if self.friendsButton.selected == false {
self.friendsButton.selected = true
self.peopleButton.selected = false
UIView.animateWithDuration(0.3, animations: { () -> Void in
self.customSlider.frame.origin.x = 0
})
}
}
UICollectionviewHeader + UI Button
UIcollectionviewHeader are using dequeueReusableSupplementaryView and for some reason it created a UIView Infront of every Headerview, this would block all gesture that are happening. the solution is to bring the view to front like so:
override func awakeFromNib() {
super.awakeFromNib()
self.bringSubview(toFront: friendsButton) // this depends on you .xib
}
this will solve your gesture issue.
Theres Multiple way to Create a UIButton Action inside a CollectionViewHeader.
AddTarget (as Answered by #derdida)
Drag and Drop Action In CollectionViewHeader Class.
Add Target :-(refer to derdida answer)
inside of viewForSupplementaryElementOfKind// ps: you need to register your CollectionView Class & Nib
Add Target to UIButton like so.
header.friendsButton.tag = 0
header.friendsButton.addTarget(self, action: #selector(self.HeaderClick(_:)), for: .touchUpInside)
Created the Function like so.
#objc func HeaderClick1(_ sender : UIButton){ print("sender.tag \(sender.tag)") }//sender.tag 0
Drag and Drop Action In CollectionViewHeader Class.
for this example i will used #Wayne Filkins Question.
Ctrl + Drag UIButton To Header Class
Select Connection Type to Action create the function like so
#IBAction func friendsButtonTapped(_ sender: Any) { print("something from friendsButtonTapped") }
and thats it.
but the main key here is to get the view to be to front. used debug view hierarchy to know more.
Someone can fixed my code view please!
Update Solution
Using UIButtonSetOnClickListener.Swift and heres the code:
extension UIControl {
func setOnClickListener(for controlEvents: UIControl.Event = .primaryActionTriggered, action: #escaping () -> ()) {
removeTarget(nil, action: nil, for: .allEvents)
let sleeve = ClosureSleeve(attachTo: self, closure: action)
addTarget(sleeve, action: #selector(ClosureSleeve.invoke), for:
controlEvents)
}
}
class ClosureSleeve {
let closure: () -> ()
init(attachTo: AnyObject, closure: #escaping () -> ()) {
self.closure = closure
objc_setAssociatedObject(attachTo, "[\(arc4random())]", self, .OBJC_ASSOCIATION_RETAIN)
}
#objc func invoke() {
closure()
}
}
Here how to used is:
Add Target :-(refer to derdida answer)
inside of viewForSupplementaryElementOfKind// ps: you need to register your CollectionView Class & Nib
header.friendsButton.setOnClickListener {
//put your code here
}
There are 2 delegate methods who are important to decide which items will be shown. For example:
You have 2 different items, they are populated in:
let items1 = [Item]()
let items2 = [Item]()
Then you have a variable, that holds which items should be shown:
let items1Shown:Bool = true
Now implement the delegate methods with something like:
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if(items1Shown == true) {
return items1.count
} else {
return items2.count
}
}
And
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
var item:Item!
if(items1Shown == true) {
item = items1[indexPath.row]
} else {
item = items1[indexPath.row]
}
// format your cell
}
And implement any button function
func ChangeItems() {
if(items1Shown == true) {
items1Shown = false
} else {
items1Shown = true
}
// reload your collectionView
self.collectionView.reloadData()
}
Edit:
Why not giving your button a target? (Add this where you dequeue your headerView!)
headerView.friendsButton.addTarget(self, action: #selector(YourViewControllerWithFunction.cellButtonClick(_:)), forControlEvents: .TouchUpInside)
// for example:
func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView {
let headerView = collectionView.dequeueReusableSupplementaryViewOfKind(kind, withReuseIdentifier: headerId, forIndexPath: indexPath)
headerView.friendsButton.addTarget(self, action: #selector(YourViewControllerWithFunction.cellButtonClick(_:)), forControlEvents: .TouchUpInside)
return headerView
}
// Class YourViewControllerWithFunction
func cellButtonClick(button: UIButton) {
// you can do anything with that button now
}
I am trying to pass information over from a collection view cell to another view controller, then dismiss the view controller. I am trying to do this with delegation as I think this is the only way, so far I have set up my protocol like so:
#objc protocol CollectionViewImageDelegate {
optional func selectedCell(row: NSIndexPath, data: UIImage)
}
I have added the property:
var delegate : CollectionViewImageDelegate?
and then called it in the didSelectItemAtIndexPath
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
let imageData = self.sources[indexPath.row]
self.delegate?.selectedCell?(indexPath, data: imageData)
self.dismissViewControllerAnimated(true, completion: nil)
}
Then how do I call it other View Controller so far I have done:
class ProfileViewController: UIViewController, CollectionViewImageDelegate {
var colViewImage : CollectionViewVC
in the view did load:
colViewImage.delegate = self
and this but nothing happens:
func selectedCell(row: NSIndexPath, data: UIImage) {
println(data) //Nothing prints
}
Nothing is printing I don't know why?
I'm new to protocol and delegates etc and can't seem to work out why it isnt passing over the image??
Thanks
Your CollectionViewController is calling this selectedCell method but you haven't linked the delegate yet.
Make your other ViewController conform to collectionViewImageDelegate (which should conventionally be spelled with a capital C) and implement your method selectedCell in it.
Set the ViewController as delegate of the CollectionViewController.
Also, change self.delegate?.selectedCell!(indexPath, data: imageData) into self.delegate?.selectedCell?(indexPath, data: imageData) to call the optional method selectedCell only if it was implemented by the delegate.