I have a UICollectionViewCell subclass that is contained within a UICollectionView as follows:
class MyCell: UICollectionViewCell {
}
class MyViewController: UIViewController, UICollectionViewDataSource {
#IBOutlet public var collectionView: UICollectionView!
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = self.collectionView.dequeueReusableCell(withReuseIdentifier: "MyCell", for: indexPath) as? MyCell else { fatalError("Unexpected indexPath") }
return cell
}
}
Is there any way to access the collectionView instance defined in MyViewController from within MyCell?
Thanks for any help.
You can try
class MyCell: UICollectionViewCell, UICollectionViewDataSource {
weak var outerCollectionView: UICollectionView!
}
and set it in cellForRowAt
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = self.collectionView.dequeueReusableCell(withReuseIdentifier: "MyCell", for: indexPath) as? MyCell else { fatalError("Unexpected indexPath") }
cell.outerCollectionView = collectionView
return cell
}
//
Or you can try to cast the superView like this
let collectionView = self.superView as! UICollectionView
Try something like below
protocol MyCellDelegate {
func reloadData()
}
class MyCell: UICollectionViewCell {
var delegate: MyCellDelegate!
}
class MyViewController: UIViewController {
//lets say this is your view controller and datasource
}
extension MyViewController: UICollectionViewDataSource {
//Methods are not implemeted here
//But you can set the delegate of the cell in cellForRowAtIndex method which is your view controller
}
extension MyViewController: MyCellDelegate {
func reloadData() {
//Here you reload the collection view
}
}
Following suggestions in the comments:
extension UICollectionViewCell {
/// The collection view that presents this cell.
weak var collectionView: UICollectionView! {
return self.superview as! UICollectionView
}
}
Related
I inserted Label into CollectionView cell but when I made outlet this error appeared.
error: The currencyLabel outlet from the ViewController to the UILabel is invalid. Outlets cannot be connected to repeating content
class ViewController: UIViewController {
var currency = Currency.getCurrency()
#IBOutlet var currencyLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return currency.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "currencyCell", for: indexPath) as! CurrencyCollectionViewCell
let currencyList = currency[indexPath.row]
currencyLabel.text = "\(currencyList.currency) \n \(currencyList.cash)"
return cell
}
}
You need to create IBOutlet of your currencyLabel in CurrencyCollectionViewCell class, and use it like
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "currencyCell", for: indexPath) as! CurrencyCollectionViewCell
let currencyList = currency[indexPath.row]
cell.currencyLabel.text = "\(currencyList.currency) \n \(currencyList.cash)"
return cell
}
You have made a simple mistake.
You should make below outlet in CollectionViewCell not in your ViewController.
#IBOutlet var currencyLabel: UILabel!
I have a UICollectionView embedded in a UITableViewCell & I want to perform a segue when a UICollectionViewCell is pressed & pass the data represented by that cell to the destination ViewController for detailed information.
Here is the code for the embedded UICollectionView inside the UITableViewCell
#IBOutlet weak var EventCollection: UICollectionView!
var events = [Events]()
override func awakeFromNib() {
super.awakeFromNib()
EventCollection.delegate = self
EventCollection.dataSource = self
}
extension PopularCell: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return events.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = EventCollection.dequeueReusableCell(withReuseIdentifier: "EventCell", for: indexPath) as! EventCell
let event = events[indexPath.row]
print("Event Name:\(event.event_name)")
cell.event = event
cell.tag = indexPath.row
return cell
}
How do I perform & prepare a segue in the main ViewController when a UICollectionViewCell is pressed so as to pass the data contained by that cell to the destination ViewController
Here are the steps that you need to do.
As you said, your CollectionView is inside TableView. So your TableView delegates/DataSources binded with the MainViewController. CollectionView delegates/DataSources binded with the TableViewCell.
Now create a protocol to know user has clicked on collectionView.
protocol MyProtocol : class {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
}
In TableViewCell, you need to call delegate like this,
class MyTableCell : UITableViewCell, UICollectionViewDelegate {
weak var delegate : MyProtocol?
:
:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if let delegate = delegate {
delegate.collectionView(collectionView, didSelectItemAt: indexPath)
}
}
}
Now your MainViewController must have to conform this protocol,
class MainViewController :UIViewController, MyProtocol {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// Do segue here ....
}
}
Note : Make sure to bind delegate with your MainViewController i.e. in TableviewCellForRow have cell.delegate = self
Add the following code with in a new file named UIView_Ext
extension UIView {
var parentViewController: UIViewController? {
var parentResponder: UIResponder? = self
while parentResponder != nil {
parentResponder = parentResponder!.next
if let viewController = parentResponder as? UIViewController {
return viewController
}
}
return nil
}
}
In func didSelectItem(At indexPath: IndexPath) method , write the following code
self.parentViewController?.performSegue(withIdentifier: "Identifer", sender: "Your Data in place of this string")
I would like to create UICollectionView with header. I set Collection Reusable View on mainStoryBoard but, nothing is shown on device. I tried to search but could not find out why it is not appearing. I
Main Story Board
On device
import UIKit
class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
#IBOutlet weak var collectionView: UICollectionView!
var images = ["medal1","medal2","medal3","medal4","medal5","medal6","medal7","medal8","medal9","medal10","medal1","medal2","medal3","medal14"]
var texts = ["hi","yes","hoo","such","hi","yes","hoo","such","hi","yes","hoo","such","hi","yes"]
override func viewDidLoad() {
super.viewDidLoad()
collectionView.delegate = self
collectionView.dataSource = self
}
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return images.count
}
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCell", for: indexPath) as! CustomCell
cell.myImage.image = UIImage(named: images[indexPath.row])
cell.achievementLabel.text = texts[indexPath.row]
return cell
}
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
}
import UIKit
Class for collection View
class CustomCell: UICollectionViewCell {
#IBOutlet weak var myImage: UIImageView!
#IBOutlet weak var achievementLabel: UILabel!
}
class for Collection Reusable View
import UIKit
class CollectionReusableView: UICollectionReusableView {
#IBOutlet weak var reuseableVimage: UIImageView!
}
> import UIKit
class ViewController: UIViewController, UICollectionViewDelegate {
#IBOutlet weak var collectionView: UICollectionView!
var images = ["medal1","medal2","medal3","medal4","medal5","medal6","medal7","medal8","medal9","medal10","medal1","medal2","medal3","medal14"]
var texts = ["hi","yes","hoo","such","hi","yes","hoo","such","hi","yes","hoo","such","hi","yes"]
override func viewDidLoad() {
super.viewDidLoad()
collectionView.delegate = self
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
if kind == UICollectionElementKindSectionHeader {
let view = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "HeaderView", for: indexPath)
// do any programmatic customization, if any, here
return view
}
fatalError("Unexpected kind")
}
}
You have to implement viewForSupplementaryElementOfKind:
Implement collectionView(_:viewForSupplementaryElementOfKind:at:), for UICollectionElementKindSectionHeader or UICollectionElementKindSectionFooter, as appropriate.
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
if kind == UICollectionElementKindSectionHeader {
let view = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "CollectionReusableView", for: indexPath) as! CollectionReusableView
// do any programmatic customization, if any, here
return view
}
fatalError("Unexpected kind")
}
Make sure the header reusable view in IB has
the appropriate base class; and
the appropriate "reuse identifier"
In IB, make sure the collection view's "Accessories" have checkmarks next "Section Header" and "Section Footer", as appropriate.
Try to implement this. There's an example in RayWenderlich that could be useful for you: https://www.raywenderlich.com/136161/uicollectionview-tutorial-reusable-views-selection-reordering
override func collectionView(_ collectionView: UICollectionView,
viewForSupplementaryElementOfKind kind: String,
at indexPath: IndexPath) -> UICollectionReusableView {
switch kind {
case UICollectionElementKindSectionHeader:
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind,
withReuseIdentifier: "CollectionReusableView",
for: indexPath) as! CollectionReusableView
headerView.reuseableVimage ....
return headerView
default:
assert(false, "Unexpected element kind")
}
}
I have a UICollectionViewCell, with a UIButton. And I have two different actions. The first one, when the user presses the cell, it will segue to another view withdidSelectITemAt; the second one, when the users presses the UIButton inside the cell.
My problem is that on Swift Code of MyCollectionViewCell, I cant perform a segue, When I write the Code:
self.performSegue(withIdentifier: "toStoreFromMyDiscounts", sender: nil)
it says the error:
Value of Type MyCollectionViewCell has no member performSegue.
I also cannot write prepareForSegue, it doesn't auto complete.
How can I create a segue from a cell, that is different from click the cell itself?
You can not call performSegue from your UICollectionViewCell subclass, because there is no interface declared on UICollectionViewCell like that.
The reason why it is working didSelectItemAtIndexPath() is because i suppose the delegate of your UICollectionView is a UIViewController subclass, what has the function called performSegueWithIdentifier:()`.
You need to notify your UIViewController when the button was clicked in your UICollectionViewCell, for what you have various possibilities, like KVO or using delegate.
Here is a little code sniplet, how to use KVO. This solution is great, as long as you do not care, in which cell was the button pressed.
import UIKit
class CollectionViewCell: UICollectionViewCell {
#IBOutlet weak var button: UIButton!
}
class CollectionViewController: UIViewController {
#IBOutlet weak var collectionView: UICollectionView!
}
extension CollectionViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell: CollectionViewCell = self.collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! CollectionViewCell
// Add your `UIViewController` subclass, `CollectionViewController`, as the target of the button
// Check out the documentation of addTarget(:) https://developer.apple.com/reference/uikit/uicontrol/1618259-addtarget
cell.button.addTarget(self, action: #selector(buttonTappedInCollectionViewCell), for: .touchUpInside)
return cell
}
func buttonTappedInCollectionViewCell(sender: UIButton) {
self.performSegue(withIdentifier: "toStoreFromMyDiscounts", sender: nil)
}
}
EDIT:
If you care, in which cell the touch event has happend, use the delegate pattern.
import UIKit
protocol CollectionViewCellDelegate: class {
// Declare a delegate function holding a reference to `UICollectionViewCell` instance
func collectionViewCell(_ cell: UICollectionViewCell, buttonTapped: UIButton)
}
class CollectionViewCell: UICollectionViewCell {
#IBOutlet weak var button: UIButton!
// Add a delegate property to your UICollectionViewCell subclass
weak var delegate: CollectionViewCellDelegate?
#IBAction func buttonTapped(sender: UIButton) {
// Add the resposibility of detecting the button touch to the cell, and call the delegate when it is tapped adding `self` as the `UICollectionViewCell`
self.delegate?.collectionViewCell(self, buttonTapped: button)
}
}
class CollectionViewController: UIViewController {
#IBOutlet weak var collectionView: UICollectionView!
}
extension CollectionViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell: CollectionViewCell = self.collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! CollectionViewCell
// Asssign the delegate to the viewController
cell.delegate = self
return cell
}
}
// Make `CollectionViewController` confrom to the delegate
extension CollectionViewController: CollectionViewCellDelegate {
func collectionViewCell(_ cell: UICollectionViewCell, buttonTapped: UIButton) {
// You have the cell where the touch event happend, you can get the indexPath like the below
let indexPath = self.collectionView.indexPath(for: cell)
// Call `performSegue`
self.performSegue(withIdentifier: "toStoreFromMyDiscounts", sender: nil)
}
}
Here's an elegant solution that only requires a few lines of code:
Create a custom UICollectionViewCell subclass
Using storyboards, define an IBAction for the "Touch Up Inside" event of your button
Define a closure
Call the closure from the IBAction
Swift 4+ code
class MyCustomCell: UICollectionViewCell {
static let reuseIdentifier = "MyCustomCell"
#IBAction func onAddToCartPressed(_ sender: Any) {
addButtonTapAction?()
}
var addButtonTapAction : (()->())?
}
Next, implement the logic you want to execute inside the closure in your
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MyCustomCell.reuseIdentifier, for: indexPath) as? MyCustomCell else {
fatalError("Unexpected Index Path")
}
// Configure the cell
// ...
cell.addButtonTapAction = {
// implement your logic here, e.g. call preformSegue()
self.performSegue(withIdentifier: "your segue", sender: self)
}
return cell
}
You can use this approach also with table view controllers.
Another solution that also works like a charm:
extension YOURViewController : UICollectionViewDataSource
{
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "YOURCell", for: indexPath) as! YOURCollectionViewCell
cell.butTapped = {
[weak self] (YOURCollectionViewCell) -> Void in
// do your actions when button tapped
}
}
return cell
}
class YOURCollectionViewCell: UICollectionViewCell
{
var butQRTapped: ((YOURCollectionViewCell) -> Void)?
#IBAction func deleteButtonTapped(_ sender: AnyObject) {
butTapped?(self)
}
}
I have a tableView in which I have 2 custom cells, in both cells I have different CollectionView, the dataSource and delegate of both the CollectionViews is my ViewController.
So now how do I check which CollectionViews is to be configured in UICollectionView's respective methods?
Here is the view hierarchy
How do I come to know which is the collectionView that's there in the parameter of above function?
So here is my code:
class FeatureBannerTableViewCell: UITableViewCell {
#IBOutlet weak var featureCollectionView: UICollectionView!
}
class FeaturedTableViewCell: UITableViewCell {
#IBOutlet weak var FeatureTitle: UILabel!
#IBOutlet weak var seeAllButton: UIButton!
#IBOutlet weak var collectionView: UICollectionView!
}
extension ViewController: UITableViewDataSource {
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
switch indexPath.row {
case 2,3,6,7:
let cell = featuredTableView.dequeueReusableCellWithIdentifier("FeatureBannerTableViewCell", forIndexPath: indexPath) as! FeatureBannerTableViewCell
cell.featureCollectionView.dataSource = self
cell.featureCollectionView.delegate = self
return cell
default:
let cell = featuredTableView.dequeueReusableCellWithIdentifier("FeaturedTableViewCell", forIndexPath: indexPath) as! FeaturedTableViewCell
cell.collectionView.dataSource = self
cell.collectionView.delegate = self
return cell
}
}
}
extension ViewController: UICollectionViewDataSource {
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// how do I know if this collectionView is collectionView or featureCollectionView?
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
// how do I know if this collectionView is collectionView or featureCollectionView?
}
}
I would use the tag property in UIView to identify your collection view. So when you configure your table cells in cellForRowAtIndexPath get the collection view of the cell and set its tag to something unique (I would use the row value of indexpath).
extension ViewController: UITableViewDataSource {
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
switch indexPath.row {
case 2,3,6,7:
let cell = featuredTableView.dequeueReusableCellWithIdentifier("FeatureBannerTableViewCell", forIndexPath: indexPath) as! FeatureBannerTableViewCell
cell.featureCollectionView.dataSource = self
cell.featureCollectionView.delegate = self
cell.featureCollectionView.tag = indexPath.row
return cell
default:
let cell = featuredTableView.dequeueReusableCellWithIdentifier("FeaturedTableViewCell", forIndexPath: indexPath) as! FeaturedTableViewCell
cell.collectionView.dataSource = self
cell.collectionView.delegate = self
cell.collectionView.tag = indexPath.row
return cell
}
}
}
Then in your collection view methods get the tag value and that will tell you which one of the many collection views it is.
extension ViewController: UICollectionViewDataSource {
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// how do I know if this collectionView is collectionView or featureCollectionView?
let datasourceIndex = collectionView.tag
// now use your datasource for the following index
}
}
I think, you can make use of isKindOfClass
Example:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// get a reference to our storyboard cell
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MyReuseIdentifier", for: indexPath as IndexPath) as! UICollectionViewCell
if cell.isKindOfClass(FeaturedBannerCollectionCell){
//implementation code
} else cell.isKindOfClass(FeaturedCollectionViewCell) {
//implementation code
}
return cell
}