I have a collection view with lots of different pictures, and I want that when a picture is tapped, a border comes around it.
I made a custom cell, and a UICollectionViewCell with the imageView embedding in another view (which is the outline) for the pic. So I set up a UITapGestureRecognizer which works to get the index, but when I set the outer view to have a boder it doesn't work.
Here is my Cell:
import UIKit
class PicViewCell: UICollectionViewCell {
#IBOutlet weak var imageView: UIImageView!
#IBOutlet weak var imageOutline: UIView!
}
This is in my UICollectionViewController:
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! PicViewCell
// Configure the cell
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(sender:)))
cell.imageOutline.addGestureRecognizer(tapGesture)
return cell
Here is the method for the tapGesture
func handleTap(sender: UITapGestureRecognizer) {
print("tap")
if let indexPath = self.collectionView?.indexPathForItem(at: sender.location(in: self.collectionView)) {
let cell = collectionView?.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! PicViewCell
cell.imageOutline.layer.borderWidth = 5
} else {
print("")
}
}
In your handleTap routine, you're calling the wrong method to retrieve the cell. You want cellForItem(at:) which will retrieve the existing cell or return nil if it isn't onscreen:
let cell = collectionView?.cellForItem(at: indexPath) as! PicViewCell
Alternate solution
If you are modifying the view you are tapping, you can access it as the view of the sender:
func handleTap(sender: UITapGestureRecognizer) {
if let imageOutline = sender.view as? UIImageView {
imageOutline.layer.borderWidth = 5
}
}
Note: In both solutions, you should update your model when a cell is outlined so that if that cell scrolls off the screen and then back onto the screen, you can use that model data to set the outline for the cell correctly in your override of collectionView(_:cellForItemAt:).
Related
I have created xib collectionview cell.. and i am able to use all its values in HomeVC like below
class HomeVC: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource,UICollectionViewDelegateFlowLayout{
#IBOutlet var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
let nib = UINib(nibName: "MainCollectionViewCell", bundle: nil)
collectionView.registerNib(nib, forCellWithReuseIdentifier: "MainCollectionViewCell")
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("MainCollectionViewCell", forIndexPath: indexPath) as! MainCollectionViewCell
cell.arrivingLabel.text = indexData.arriv
cell.lblDiscountedPrice.text = indexData.discPrice
return cell
}
like below i can give action to xib cell button, but i want xib cell button action in HomeVC class how, please guide me here
cell.btnDetails.addTarget(self, action: #selector(connected(sender:)), for: .touchUpInside)
#objc func connected(sender: UIButton){
}
i want like this in HomeVC
#IBAction func productDetailsMain(_ sender: UIButton){
}
note: if i use same collectionview cell then if i drag from HomeVC button action outlet to collectionview cell button then its adding.. but if i use xib cell in collectionview then this process is not working.. how to give xib cell button action in HomeVC class
You have to use closure.
cell class add this property
var connectionButtonAction: (() -> Void)?
and on the button action make this
#objc func connected(sender: UIButton){
connectionButtonAction?()
}
so finally for on the cell creation you have add this:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("MainCollectionViewCell", forIndexPath: indexPath) as! MainCollectionViewCell
cell.arrivingLabel.text = indexData.arriv
cell.lblDiscountedPrice.text = indexData.discPrice
cell.connectionButtonAction = { [weak self] in
print("cell button pressed")
}
return cell
}
I think this is the simple way, but also you get the same approach using delegates.
If you want to have the collection view cell's button trigger an action in the view controller that own's the collection view, you need to add the view controller as the target. You can do that in your cellForItemAtIndexPath, but you will need to remove the previous target/action so you don't keep adding a new target/action each time you reuse a cell:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("MainCollectionViewCell", forIndexPath: indexPath) as! MainCollectionViewCell
cell.arrivingLabel.text = indexData.arriv
cell.lblDiscountedPrice.text = indexData.discPrice
// Remove the previous target/action, if any
cell.btnDetails.removeTarget(nil, action: nil,for: .allEvents)
// Add a new target/action pointing to the method `productDetailsMain(_:)`
cell.btnDetails.addTarget(self, action: #selector(productDetailsMain(_:)), for: .touchUpInside)
return cell
}
You could also set up your cells to hold a closure as in luffy_064's answer.
Note that if you are running on iOS 14 or later, you can use the new addAction(_:for:) method of UIControl to add a UIAction to your button. (UIActions include a closure.)
That might look like this:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("MainCollectionViewCell", forIndexPath: indexPath) as! MainCollectionViewCell
cell.arrivingLabel.text = indexData.arriv
cell.lblDiscountedPrice.text = indexData.discPrice
// Remove the previous target/action, if any
let identifier = UIAction.Identifier("button")
removeAction(identifiedBy: identifier, for: .allEvents)
// Add a new UIAction to the button for this cell
let action = UIAction(title: "", image: nil, identifier: identifier, discoverabilityTitle: nil, attributes: [], state: .on) { (action) in
print("You tapped button \(indexPath.row)")
// Your action code here
}
cell(action, for: .touchUpInside)
return cell
}
This question already has answers here:
How to give xib cell button action in HomeVC in swift
(2 answers)
Closed 1 year ago.
I have created xib collectionview cell.. and i am able to use all its values in HomeVC like below
class HomeVC: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource,UICollectionViewDelegateFlowLayout{
#IBOutlet var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
let nib = UINib(nibName: "MainCollectionViewCell", bundle: nil)
collectionView.registerNib(nib, forCellWithReuseIdentifier: "MainCollectionViewCell")
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("MainCollectionViewCell", forIndexPath: indexPath) as! MainCollectionViewCell
cell.arrivingLabel.text = indexData.arriv
cell.lblDiscountedPrice.text = indexData.discPrice
return cell
}
like below i can give action to xib cell button, but i want xib cell button action in HomeVC class how, please guide me here
cell.btnDetails.addTarget(self, action: #selector(connected(sender:)), for: .touchUpInside)
#objc func connected(sender: UIButton){
}
i want xib cell's button action in HomeVC
#IBAction func productDetailsMain(_ sender: UIButton){
}
note: if i use same collectionview cell then i can drag from HomeVC button action outlet to collectionview cell button then its adding.. but if i use xib cell in collectionview then this process is not working.. how to give xib cell button action in HomeVC class
Screenshot: not able to connect action outlet in HomeVC
class HomeVC: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource,UICollectionViewDelegateFlowLayout{
#IBOutlet var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
let nib = UINib(nibName: "MainCollectionViewCell", bundle: nil)
collectionView.registerNib(nib, forCellWithReuseIdentifier: "MainCollectionViewCell")
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("MainCollectionViewCell", forIndexPath: indexPath) as! MainCollectionViewCell
cell.btnDetails.tag = indexPath.row// it will identify the tapped button cell index
cell.btnDetails.addTarget(self, action: #selector(connected(sender:)), for: .touchUpInside)
cell.arrivingLabel.text = indexData.arriv
cell.lblDiscountedPrice.text = indexData.discPrice
return cell
}
#objc func connected(sender: UIButton){
let tag = sender.tag
print(tag)
}
I gonna need your help to understand how to interact with an UITextField when button tapped.
This is my code in my ViewController:
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! MyCollectionViewCell
cell.addBookTapped.addTarget(self,action: #selector(buttonTapped(_:)),for: .touchUpInside)
cell.configureCell(with: books[indexPath.row], indexCell: indexPath.row )
return cell
}
func buttonTapped(_ sender: UIButton)
{
let tag = sender.tag
//Do Stuff
}
And in my MyCollectionViewCell, I configure all the button and textView and add tag in my button:
#IBOutlet weak var myTextFielThatIWantToFieldWhenButtonTapped: UITextField!
In my ViewController inside my func buttonTapped I can't reach myTextFielThatIWantToFieldWhenButtonTapped.
How can write something in it when the button is tapped and be visible directly on the view?
you have to get your intended cell by calling cellForItem at an indexPath of your specific cell like below: (for example your cell is in section 0 item 0 )
let indexPath = IndexPath(item: 0, section: 0)
let cell = collectionView.cellForItem(at: indexPath) as? YourCustomCollectionViewCellClass
then you can access your variables inside your cell class :
cell.myTextFielThatIWantToFieldWhenButtonTapped
I have 2 files.
myTableViewController.swift
myTableCell.swift
Can I get the indexPath.row in myTabelCell.swift function?
Here is myTableCell.swift
import UIKit
import Parse
import ActiveLabel
class myTableCell : UITableViewCell {
//Button
#IBOutlet weak var commentBtn: UIButton!
#IBOutlet weak var likeBtn: UIButton!
#IBOutlet weak var moreBtn: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
}
#IBAction func likeBtnTapped(_ sender: AnyObject) {
//declare title of button
let title = sender.title(for: UIControlState())
//I want get indexPath.row in here!
}
Here is myTableViewController.swift
class myTableViewController: UITableViewController {
//Default func
override func viewDidLoad() {
super.viewDidLoad()
//automatic row height
tableView.estimatedRowHeight = 450
tableView.rowHeight = UITableViewAutomaticDimension
}
// cell config
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//define cell
let cell = tableView.dequeueReusableCell(withIdentifier: "myTableCell", for: indexPath) as! myTableCell
}
As you can see... I'm trying to get indexPath.row in myTableCell, liktBtnTapped function.
Could you let me know how can I access or get IndexPath.row?
I have created a UIResponder extension with a recursive method that you can use in any UIView (which inherits from UIResponder) to find a parent view of a specific type.
import UIKit
extension UIResponder {
/**
* Returns the next responder in the responder chain cast to the given type, or
* if nil, recurses the chain until the next responder is nil or castable.
*/
func next<U: UIResponder>(of type: U.Type = U.self) -> U? {
return self.next.flatMap({ $0 as? U ?? $0.next() })
}
}
Using this, we can extend UITableViewCell with some convenient read-only computed properties for the table view and index path of the cell.
extension UITableViewCell {
var tableView: UITableView? {
return self.next(of: UITableView.self)
}
var indexPath: IndexPath? {
return self.tableView?.indexPath(for: self)
}
}
Here is how you could use it in your example:
#IBAction func likeBtnTapped(_ sender: AnyObject) {
//declare title of button
let title = sender.title(for: UIControlState())
//I want get indexPath.row in here!
self.indexPath.flatMap { print($0) }
}
Swift 4+
Try this inside your cell.
func getIndexPath() -> IndexPath? {
guard let superView = self.superview as? UITableView else {
print("superview is not a UITableView - getIndexPath")
return nil
}
indexPath = superView.indexPath(for: self)
return indexPath
}
Easy.. You can do like this inside button action:
let section = 0
let row = sender.tag
let indexPath = IndexPath(row: row, section: section)
let cell: myTableCell = self.feedTableView.cellForRow(at: indexPath) as! myTableCell
And afterwards in cellForRowAtIndexPath:
// add the row as the tag
cell.button.tag = indexPath.row
Another Approach for Swift 4.2 and not assuming Superview will be always a tableview
extension UITableViewCell{
var tableView:UITableView?{
return superview as? UITableView
}
var indexPath:IndexPath?{
return tableView?.indexPath(for: self)
}
}
Usage example
#IBAction func checkBoxAction(_ sender: UIButton) {
guard let indexPath = indexPath else { return }
sender.isSelected = !sender.isSelected
myCustomCellDelegate?.checkBoxTableViewCell(didSelectCheckBox: sender.isSelected, for: indexPath)
}
Swift 4.1. Here I created function to get IndexPath. Just pass your UIView(UIButton,UITextField etc) and UITableView object to get IndexPath.
func getIndexPathFor(view: UIView, tableView: UITableView) -> IndexPath? {
let point = tableView.convert(view.bounds.origin, from: view)
let indexPath = tableView.indexPathForRow(at: point)
return indexPath
}
Create a property indexPath in the cell class and set it in cellForRowAtIndexPath when the cell is reused.
But there is a caveat: Some table view methods to rearrange the cells don't call cellForRowAtIndexPath. You have to consider this case.
But if you use always only reloadData() it's safe and pretty easy.
Another way is to put the code regarding controlling things back in the controller class and run it via callback closures capturing the index path.
Heres another way of doing it
import UIKit
import Parse
import ActiveLabel
class myTableCell : UITableViewCell {
//Button
#IBOutlet weak var commentBtn: UIButton!
#IBOutlet weak var likeBtn: UIButton!
#IBOutlet weak var moreBtn: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
}
}
class myTableViewController: UITableViewController {
//Default func
//assuming you have an array for your table data source
var arrayOfTitles = [String]()
override func viewDidLoad() {
super.viewDidLoad()
//automatic row height
tableView.estimatedRowHeight = 450
tableView.rowHeight = UITableViewAutomaticDimension
}
// cell config
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//define cell
let cell = tableView.dequeueReusableCell(withIdentifier: "myTableCell", for: indexPath) as! myTableCell
cell.commentBtn.tag = indexPath.row
cell.commentBtn.addTarget(self, action: #selector(likeBtnTapped(_:), forControlEvents:.TouchUpInside)
//cell config end
#IBAction func likeBtnTapped(sender: UIButton) {
let btn = sender
let indexP = NSIndexPath(forItem: btn.tag, inSection: 0)
let cell = tableView.dequeueReusableCell(withIdentifier: "myTableCell", for: indexP) as! myTableCell
//I want get indexPath.row in here!
let title = arrayOfTitles[indexP.row]
//declare title of button
cell.commentBtn.setTitle(title, forState: UIControlState.Normal)
}
}
My solution was subclassing UITableViewCell, so can add IndexPath property. assign custom class for table view cell in storyboard. assign IndexPath value when rowAtIndexPath called.
class MyTableViewCell: UITableViewCell {
var indexPath: IndexPath?
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: "cellid1", for: indexPath)
(cell as? MyTableViewCell)?.indexPath = indexPath
return cell
}
Swift 5:
if
let collectionView = superview as? UICollectionView,
let index = collectionView.indexPath(for: self)
{
// stuff
}
When i tap on a cell i want to receive the index or other identifier specific to that cell. The code works and goes in the tapped function. But how can i receive a index or something like that?
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("ShowCell", forIndexPath: indexPath) as UICollectionViewCell
if cell.gestureRecognizers?.count == nil {
let tap = UITapGestureRecognizer(target: self, action: "tapped:")
tap.allowedPressTypes = [NSNumber(integer: UIPressType.Select.rawValue)]
cell.addGestureRecognizer(tap)
}
return cell
}
func tapped(sender: UITapGestureRecognizer) {
print("tap")
}
Swift 4 - 4.2 - Returns index of tapped cell.
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cellIndex = indexPath[1] // try without [1] returns a list.
print(indexPath[1])
chapterIndexDelegate.didTapChapterSelection(chapterIndex: test)
}
Build on Matts answer
first off we add the tap recognizer
let tap = UITapGestureRecognizer(target: self, action: #selector(self.tapped(tapGestureRecognizer:)))
cell.textView.addGestureRecognizer(tap)
Then we use the following function to get the indexPath
#objc func tapped(tapGestureRecognizer: UITapGestureRecognizer){
//textField or what ever view you decide to have the tap recogniser on
if let textField = tapGestureRecognizer.view as? UITextField {
// get the cell from the textfields superview.superview
// textField.superView would return the content view within the cell
if let cell = textField.superview?.superview as? UITableViewCell{
// tableview we defined either in storyboard or globally at top of the class
guard let indexPath = self.tableView.indexPath(for: cell) else {return}
print("index path =\(indexPath)")
// then finally if you wanted to pass the indexPath to the main tableView Delegate
self.tableView(self.tableView, didSelectRowAt: indexPath)
}
}
}
Think about it. The sender is the tap gesture recognizer. The g.r.'s view is the cell. Now you can ask the collection view what the index path of this cell is (indexPathForCell:).