Is it possible to reuse cellForItemAtIndexPath:? - ios

I have two collection views. They both use identical cell layouts but for other reasons they must be separate controllers. Currently when I make updates to cellForItemAtIndexPath: in one, I have to copy and paste the change to the other.
Is there a way for me to separate out this one method so I can make changes in one place and have them reflected in both controllers? One of the controllers is a UIViewController subclass. The other is a subclass of a custom UIViewController subclass.

You can make a Separate Data Source class then you can just link the collectionview data source to it.
Your data source will be like this
class CollectionViewDataSource: NSObject, UICollectionViewDataSource {
var dataArray:[YourObjects]?
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return dataArray!.count ?? 0
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("YourCellIdentifier", forIndexPath: indexPath) as! UICollectionViewCell
return cell
}
}
And you can cell it in your controller like this
lazy var collectionViewDataSource: CollectionViewDataSource = {
return CollectionViewDataSource()
}()
#IBOutlet weak var collectionView: UICollectionView! {
didSet{
collectionViewDataSource.dataArray = //Your array
collectionView.dataSource = collectionViewDataSource
}

You can achieve this
Suppose there are 2 viewcontroller vc1 and vc2 and 2 collectionview cv1 and cv2
Make a separate class
CollectionDelegate cD
now set datasource of both collectionview to this newly made CollectionDelegate
like
cv1.dataSource = cD
cv2.datasource = cD
Implement you cellForItemAtIndexPatha and other data source method in CollectionDelegate class

Related

How to use custom UICollectionReusableView as section header of collection view?

I've been driven insane for hours as I can't get around with the issue.
I have a collection view which can have different section with different no. of items in each. For each section I need to use a section header of different type. So for this, I'm going to use UICollectionReusableView. But I can't seem to succeed in using a custom subclass of UICollectionReusableView by means of UINib registration.
A crash happens when I downcast the reusable view to my subclass. Like:
let friendHeader = collectionView.dequeueReusableSupplementaryView(ofKind: kind,
withReuseIdentifier: "FriendHeaderView",
for: indexPath) as! FriendHeaderView
Below is the code snippet:
class ViewController: UIViewController {
#IBOutlet weak var collectionView: UICollectionView!
private let viewModel = ProfileViewModel()
override func viewDidLoad() {
super.viewDidLoad()
collectionView.dataSource = self
collectionView.delegate = self
// more code
collectionView.register(UINib(nibName: "FriendHeaderView", bundle: nil),
forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader,
withReuseIdentifier: "FriendHeaderView")
}
}
Now here is the data source implementation:
extension ViewController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
// valid implementation
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// valid implementation
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// valid implementation
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
switch kind {
case UICollectionView.elementKindSectionHeader:
let friendHeader = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "FriendHeaderView", for: indexPath) as! FriendHeaderView
// *** Crash happens here *** //
return friendHeader
default:
assert(false, "Invalid element type")
}
}
}
And I don't know why the collectionView(_:layout:referenceSizeForHeaderInSection:) needs to be also implemented. So here it is:
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
let size = CGSize(width: collectionView.bounds.width, height: 100)
return size
}
}
Okay, now come to the point: The above mentioned crash doesn't happen
at all if I don't downcast with as! operator. Well, if I use section
header from the storyboard instead of UINib registration, there is
no crash.
If I'm going to need multiple type header, then I can't also use storyboard approach or without down-casting approach as I need to feed data to those headers.
What can I do to have multiple type headers with view built from interface builder?
I've made a demo project with what I've said above. If anyone is interested please check out that.
Once you assign proper class and identifier in your Xib file, then it will work without crashes.
Well, after some more investigation and the input from #good4pc in the accepted answer (actually I found out that by myself before looking at the answer) it seems that the issue is actually happening for some unwanted issue with Xcode.
When we create any view (preferably, UITableViewCell or UICollectionViewCell) with .xib, the class identity is provided automatically for that .xib in the identity inspector. But this was not the case for UICollectionReusableView. See the attached screenshot below for easy understanding.
This is a UICollectionViewCell subclassed with .xib:
This is a UICollectionReusableView subclassed with .xib:
So the key is to provide the class identity of the .xib file which
is done from the attributes inspector.

UICollectionViewCell with image without custom Class

there pretty simple Question actually:
I want a collectionView with an Image with the same size as the Cell. I want to do this all without building a extra class, so from storyboard (maybe, if I get on my subview?!)
And btw, i load data from web, so I Can't just add a new ImageView every time....
just wanna ask if this is possible?!
Cheers
If you have static Collection view then you can do without custom class
but you have dynamic Collection view then you need to create custom class.
which is look like this.
class AddAlbumCell: UICollectionViewCell {
#IBOutlet var imgv: UIImageView!
}
In your Collection view Delegate method
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! AddAlbumCell
cell.imgv.image = images[indexPath.row] //"images" contains image name array
return cell;
}

tapping on collection view item causes multiple selection in collection view, instead of single selection

I have one UICollectionView and a custom cell for it. The custom cell contains one UIImageView and a UILabel.
I want to change the background color of the UIImageView, when I tap that corresponding cell ( In didSelectItemAt indexPath method I want to access the custom cell properties). Is there any way to achieve this? Or should I need some alternate way?
Another problem I am getting that when I tap any item from that UICollectionView, multiple selection is happening. Means the reusable cells are also getting selected as well.
Can anybody help me?
Try This:
1. Custom UICollectionViewCell class:
import UIKit
class CustomCollectionViewCell: UICollectionViewCell
{
//MARK: Outlets
#IBOutlet weak var testImageView: UIImageView!
#IBOutlet weak var testLabel: UILabel!
//MARK: Lifecycle Methods
override func awakeFromNib()
{
self.testImageView.image = nil
self.testLabel.text = nil
}
override var isSelected: Bool{
willSet{
super.isSelected = newValue
if newValue
{
self.backgroundColor = UIColor.lightGray
}
else
{
self.backgroundColor = UIColor.groupTableViewBackground
}
}
}
}
2. Implement UICollectionViewDelegate Method as:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
{
collectionView.selectItem(at: indexPath, animated: true, scrollPosition: .centeredVertically)
}
You can change the cell selection and default colors according to your requirement.
For your first problem
Refer to this link as it contains the explanation on how to access the cell and its properties. (It is for UITableView but the same thing can be done for a UICollectionView)
For your second problem
You will have to update your model on didSelectRow and in cellForRowAtIndexPath you will have to set the color of the cell based on that model.
Another solution for your second point would be to store the selected indexPath in an
array and in your cellForRowAtIndexPath check if that indexPath
is present in that array and set its color appropriately. Example:
var array = [Int]()
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
//update model or
array.append(indexPath.item)
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
//check model or
if(array.contains(indexPath.item)){
}else{
}
}

How to push VC from Custom CollectionViewCell Which is on TableViewCell?

I am Having a tableView and Cell, on Cell i have a collectionView and Displaying Some Content On it .
I want to send a link on selection of indexPath.
I want to push/present My View from Custom CollectionViewCell which is on TableViewCell.
class secondTopicTableViewCell: UITableViewCell {
#IBOutlet weak var relatedCustom: UICollectionView!
var relArray = NSArray()
func loadArray(arr: NSArray) {
self.relArray = arr
self.relatedCustom.reloadData()
}
}
extension secondTopicTableViewCell : UICollectionViewDataSource {
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return relArray.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("collection", forIndexPath: indexPath) as! relatedCollectionViewCell
let info = self.relArray.objectAtIndex(indexPath.row) as! specificTopicInfo
cell.showInfo(info)
return cell
}
}
extension secondTopicTableViewCell : UICollectionViewDelegate {
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
let relatedTopic = self.relArray.objectAtIndex(indexPath.row) as! specificTopicInfo
let str = relatedTopic.relatedLink!
print(str)
}
}
class relatedCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var relatedLabel: UILabel!
func showInfo(info: specificTopicInfo) {
relatedLabel.backgroundColor = UIColor.grayColor()
relatedLabel.text = info.relatedTitle
}
}
You just need to navigate using didSelectItemAtIndexPath same as Tableview control.
Write your Navigation code to didSelectItemAtIndexPath of Collectionview
If you are nesting a collection view inside a table view cell and want to trigger an action from the table view displaying this 1st cell, you can do two things.
First
You can use the UICollectionViewDelegate protocol to catch the user interaction with your collection view cell.
Don't forget to set your table view cell as the delegate of its collection view.
Second
You can create your own protocol (useful when a cell have multiple buttons).
Then, each button will have its own “didTap” method, instead of the “didSelect” method from the original collection view delegate.

Collection View Cells not appearing in Collection View?

I created a Collection View using purely the storyboard interface builder. This is what the storyboard looks like:
My collection view's properties are default as well. I haven't written anything into my ViewController.swift yet.
For some reason, when I run on my phone / emulator, none of the buttons are showing.
UICollectionView does not support static cells like UITableView. You will have to set its dataSource,delegate and configure your cells in code.
Just configure the collectionView properly see below code and image:
Implement the delegate methods of collectionView:
class yourClassController: UICollectionViewDataSource, UICollectionViewDelegate {
override func numberOfSectionsInCollectionView(collectionView:
UICollectionView!) -> Int {
return 1
}
override func collectionView(collectionView: UICollectionView!,
numberOfItemsInSection section: Int) -> Int {
return yourArray.count
}
override func collectionView(collectionView: UICollectionView!,
cellForItemAtIndexPath indexPath: NSIndexPath!) ->
UICollectionViewCell! {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("CollectionViewCell", forIndexPath: indexPath) as CollectionViewCell
// Configure the cell
cell.backgroundColor = UIColor.blackColor()
cell.textLabel?.text = "\(indexPath.section):\(indexPath.row)"
cell.imageView?.image = UIImage(named: "circle")
return cell
}
Then from your storyboard set the delegate and datasource by drag and drop see image:
Note: collectionView appears when you do complete above formality with its relevant class.

Resources