I have an ImageView inside a CollectionViewCell. I want to be able to click the image and it take me to another ViewController. How would I do this? This is the code I have so far.
import UIKit
class FirstViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
#IBOutlet weak var collectionView: UICollectionView!
var images = ["meal1", "photograph1", "meal1"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.collectionView.delegate = self
self.collectionView.dataSource = self
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return images.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionCell", for: indexPath) as! CollectionViewCell
//set images
cell.imageView.image = UIImage(named: images[indexPath.row])
return cell
}
}
As you said you want to detect image tap on collectionview cell please go through this code :
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(ViewController.connected(_:)))
cell.yourImageView.isUserInteractionEnabled = true
cell.yourImageView.tag = indexPath.row
cell.yourImageView.addGestureRecognizer(tapGestureRecognizer)
And add below method to your ViewController
func connected(_ sender:AnyObject){
print("you tap image number : \(sender.view.tag)")
//Your code for navigate to another viewcontroller
}
Note - Make sure your user interection for cell image is enable
Add a tabGestureRecognizer to your imageview in collectionView "cellForItemAt" method, and in the method of recognizer tap call the segue to go to the desired viewcontroller.
Swift 5
CollectionView Function:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(ViewController.tap(_:)))
cell.yourImg.isUserInteractionEnabled = true
cell.yourImg.tag = indexPath.row
cell.yourImg.addGestureRecognizer(tapGestureRecognizer)
return cell
}
Tap Function:
#IBAction func tap(_ sender:AnyObject){
print("ViewController tap() Clicked Item: \(sender.view.tag)")
}
You can either us a UIButton and set the image property on it with no title, or you can add a UIGestureRecognizer to the UIImageView. Either way, you'd just Present or Show the ViewController you want to display once the action has been received.
One thing I'll often do in this situation, is create a CollectionCellDelegate protocol that has a callback function (something like buttonPressed:forCollectionCell:), that I can have my CollectionView conform to, then set the delegate of each cell to the CollectionView. Then you can call up to the CollectionView when the button/image is pressed, and have the CollectionView handle whatever behaviour you want, in this case, presenting/pushing a new view controller.
Related
So i have a horizontal UICollectionView of images that is inside a vertical UICollectionView and i want to detect which cell is in the center on the horizonal UICollectionView when i select a cell from the vertical one
I tried to send a notification and call a function that does the work but its called multiple times since its reusing the same cell so at the end i dont get the appropriate indexPath.
And also when i tap on an image the "didSelectItemAt" of the horizontal collectionView is called, is there a way to get the vertical one to be called instead ?
thanks
Your best bet would be delegates via protocols
Create protocol for your collection view cells
protocol yourDelegate: class {
func didSelectCell(WithIndecPath indexPath: IndexPath, InCollectionView collectionView: UICollectionView) -> Void
}
In your cells, create a function called setup which you can call at cellForRow.
In your cells, create a touch recogniser for self.
Disable cell selection for your collection views since these delegates will be called when the user touches given cell.
class yourCell: UICollectionViewCell {
var indexPath: IndexPath? = nil
var collectionView: UICollectionView? = nil
weak var delegate: yourDelegate? = nil
override func awakeFromNib() {
super.awakeFromNib()
let selfTGR = UITapGestureRecognizer(target: self, action: #selector(self.didTouchSelf))
self.contentView.addGestureRecognizer(selfTGR)
}
#objc func didTouchSelf() {
guard let collectionView = self.collectionView, let indexPath = self.indexPath else {
return
}
delegate?.didSelectCell(WithIndecPath: indexPath, InCollectionView: collectionView)
}
func setupCell(WithIndexPath indexPath: IndexPath, CollectionView collectionView: UICollectionView, Delegate delegate: yourDelegate) {
self.indexPath = indexPath
self.collectionView = collectionView
self.delegate = delegate
}
}
In your viewController, create extension for this protocol and if you do everything right, when the user touches your cell, the cell will call you via this delegation.
extension YourViewController: yourDelegate {
func didSelectCell(WithIndecPath indexPath: IndexPath, InCollectionView collectionView: UICollectionView) {
//You have your index path and you can "if" your colllection view
if collectionView == self.yourFirstCollectionView {
} else if collectionView == self.yourSecondCollectionView {
}
//and so on..
}
}
since protocol is ": class", we can use weak for your delegate property so no memory leak will occur. I use this method for tableViews and collectionViews throughout my projects.
Having UICollectionView as a child of UITableView row. UICollectionView contains images, but whenever I scroll tableview down and up the collection view images got vanished randomly. I am attaching images for my problem reference. Please suggest me how to stop this.
I want my tableview to be like this. And its items should not change on scrolling.
----------------------------------------------------------------------------------------------------------------------------------------------
The collectionview images got vanish on scrolling tableview. It looks like this after scrolling up.
Code Is as follow:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let cell:PartOfLookTableViewCell = self.looksListTable.dequeueReusableCell(withIdentifier: "cell") as! PartOfLookTableViewCell
let oneRecord = looksArray[indexPath.row]
cell.myCollectionView.loadInitial(_dataArray: oneRecord.imagesArray, isLooks: 1)
return cell
}
Code for loading data to CollectionView:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: looksReuseIdentifier, for: indexPath) as! CustomCollectionViewCell
let oneRecord = inputArray[indexPath.row]
cell.productImage.sd_setImage(with: URL.init(string: oneRecord.thumb_url)){ (image, error, cacheType, url) in
DispatchQueue.main.async {
cell.productImage.image = image
}
}
}
}
}
#Sourabh Bissa :
UITableView reuses the cell using method CellForRowAtIndexPath whenever your new cell gets visible your this method reuse the data source.
The very important thing here is to maintain the data source:
In your case cell for the row at index path giving the updated value to the collection view method but you are not reloading in main Queue. Try to do it immediately after you get the data source.
Your Cell for the row at index path will look like this :
guard let cell = self.tableview.dequeueReusableCell(withIdentifier: "cell") as! PartOfLookTableViewCell else {
return UITableViewCell()
}
let oneRecord = looksArray[indexPath.row]
cell.configureCell(for record : oneRecord with looks : 1)
return cell
and Now in the cell, you will have collection view outlet, where you will implement a collection view data source method and there you download your images asynchronously.
Cell Class will look like this :
class PartOfLookTableViewCell: UITableViewCell {
#IBOutlet weak var collectionView: UICollectionView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
collectionView.delegate = self
collectionView.dataSource = self
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
func configureCell(for record : Record , with looks : Int) {
// Here reload your collection view
// This collection view will be specific to the cell.
collectionView.reloadData()
}
}
extension PartOfLookTableViewCell : UICollectionViewDelegate , UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
//return array
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// Asyncronously download images
}
}
This is how you can achieve your requirements without using any tags. Please let me know if you have any Queries in it.
I have a collectionView used for scrolling between pages, inside of one of these full page cells I have another collectionView with cells. How do I perform a segue when one of the cells inside of the inner most collectionView is tapped.
You will need a delegate on the cells with collection view, that will need to be notified when a particular cell is selected:
protocol CollectionCellDelegate: class {
func selectedItem()
}
class CollectionCell: UITableViewCell {
weak var delegate: CollectionCellDelegate?
// ...
func collectionView(_ collectionView: UICollectionView,
didSelectItemAt indexPath: IndexPath) {
self.delegate?.selectedItem()
}
}
And in the TableViewController you will have to implement that delegate to perform segue from it (you have to perform segue from UIViewController subclass, but the UITableViewCell is not subclassing it, that's why you need the delegate pattern).
class TableViewController: UITableViewController, CollectionCellDelegate {
// ...
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CollectionCell", for: indexPath) as! CollectionCell
// set its delegate to self
cell.delegate = self
return cell
}
func selectedItem() {
// here you can perform segue
performSegue(withIdentifier: "mySegue", sender: self)
}
}
I haven't passed any argument to the delegate, but you can of course use arguments to pass any information that you need for the segue (e.g., the id of the collection cell that was selected, etc.).
When you tap on an item in collectionView, the following delegate method will be called (if you wired up everything properly) func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)... notice that the first param is collectionView itself.
Depending on how you set it up...
if you have two collectionViews within one UIViewController then you can do..
func collectionView(_ collectionView: UICollectionView,
didSelectItemAt indexPath: IndexPath) {
if collectionView == self.innerCollectionView {
// performSegue...
}
}
if you have two view controllers, one for outer and another for inner.. then you can create use delegate pattern to let the outer know which item got selected, and segue using that info.
I have an issue where the data presented in a UICollectionView overwrites the label and the cell view is not getting cleared.
This image shows the issue,
IE:
My UICollectionViewCell which is constructed like so;
// in viewDidLoad
self.playerHUDCollectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier:reuseIdentifer)
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell:UICollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifer, for: indexPath) as UICollectionViewCell
let arr = UINib(nibName: "EYPlayerHUDView", bundle: nil).instantiate(withOwner: nil, options: nil)
let view = arr[0] as! EYPlayerHUDView
cell.contentView.addSubview(view)
if let allPlayers = self.allPlayers
{
let player:EYPlayer = allPlayers[indexPath.row]
view.updatePlayerHUD(player: player)
}
cell.layoutIfNeeded()
return cell
}
I use a view to display in the cell.
I tried removing all the cell's subchildren in the cellForItemAt but it appears to remove all the subviews.
I would like to know how do I clear the UICollectionViewCell so labels and other info on the UICollectionViewCell is not dirty like the example above.
Many thanks
Use prepareForReuse method in your custom cell class, something like this:
override func prepareForReuse() {
super.prepareForReuse()
//hide or reset anything you want hereafter, for example
label.isHidden = true
}
in your cellForItemAtIndexPath, instantiate your custom cell:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "myCellIdentifier", for: indexPath) as! CustomViewCell
Then, always in cellForItemAtIndexPath, setup your items visibility/values
//cell = UICollectionViewCell
for subview in cell.contentView.subviews {
// you can place "if" condition to remove image view, labels, etc.
//it will remove subviews of cell's content view
subview.removeFromSuperview()
}
UICollectionViewCells are reused to avoid instantiations, to optimize the performance. If you are scrolling and a cell becomes invisible, the same object is used again (dequeueReusableCell) and a new content is set in cellForItemAt...
As mentioned in the previous answers, before reusing the cell, prepareForReuse() is called on the cell. So you can overrride prepareForReuse() and do whatever preparation you need to do.
You are however creating and adding a new EYPlayerHUDView to the cell on every reuse, so your cell becomes full of stacked EYPlayerHUDViews.
To avoid this, subclass UICollectionViewCell and make the EYPlayerHUDView a property of your custom cell (I recommend to use a XIB):
class MyCell: UICollectionViewCell {
#IBOutlet var player:EYPlayerHUDView!
override func prepareForReuse() {
super.prepareForReuse()
// stop your player here
// set your label text = ""
}
}
After doing so, you can update the EYPlayerHUDView in cellForItemAt without instantiating it and without adding it as new view:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifer, for: indexPath) as? MyCell else {
return nil
}
if let allPlayers = self.allPlayers {
let player:EYPlayer = allPlayers[indexPath.row]
cell.player.updatePlayerHUD(player: player)
}
return cell
}
(Code untested)
Make custom UICollectionView class and implement prepareForReuse to clear the content if needed.
My each UICollectionViewCell class have a delete button:
#IBAction func hideSingleCampaign(sender: AnyObject) {
self.removeFromSuperview()
}
Once tapped, cell disappears, and UICollectionView is left with empty space. I have no UICollectionView reference from cell class so I can't use collectionView.reloadData()
How am I supposed to shift other cells up?
You need to remove the object from the array that you are using with your CollectionViewDataSource method instead of deleting cell, So first of all declare action of Button inside CollectionViewCell, so that add action of button in the cellForItemAtIndexPath like this.
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath:NSIndexPath)->UICollectionViewCell {
var cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as CollectionCell
cell.delBtn.tag = indexPath.item
cell.delBtn.addTarget(self, action: #selector(self.onDeletTapped(_:)),
forControlEvents: .TouchUpInside)
return cell
}
Add this method onDeletTapped on your ViewController
func onDeletTapped(sender: UIButton) {
let index = sender.tag
yourArray.removeAtIndex(index)
self.collectinView.reloadData()
}