I'm new to iOS programming but from my experience with Android i know that you can create one design for a item that can be reused in multiple lists, without needing to create a copy.
In iOS i have been trying the same approach with UICollectionView.
In my view i have created three Horizontal UICollectionViews (with different data sets) which in turn use their own cell (Which is identical to the others). I don't know how to make the other two collections use the first ones cell, so i don't need to recreate the same cell over and over.
You can't achieve this using single storyboard. If you want to use Interface Builder (xml) file for layout, you should create MyCell.xib file, then drag UICollectionViewCell on that file and work with that xib.
Then you should connect .xib file with your collection view like this:
let nib = UINib(nibName: "MyCell", bundle: nil)
collectionView?.register(nil, for: "MyCellReuseID")
And you can work with your cell now.
Also you can create MyCell: UICollectionViewCell class without creating .xib file, create views on it programmatically and then call collectionView?.register(MyCell.self, for: "MyCellReuseID")
In your function collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell you can inform your collectionview which cell to use
if you have define your cell you might have something similar to this
class MyCell: UICollectionViewCell{
var Label: UILabel!
var imageView: UIImageView!
}
so into your function collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) you need to inform your collectionview which cell to use
collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell:MyCell = collectionView.dequeueReusableCellWithReuseIdentifier("myCell", forIndexPath: indexPath) as! MyCell
[...]
}
and dont forget to register your cell first
override func viewDidLoad() {
super.viewDidLoad()
//do your stuff
collectionView.delegate = self
collectionView.dataSource = self
collectionView.backgroundColor = UIColor.whiteColor()
collectionView.registerClass(MyCell.self, forCellWithReuseIdentifier: "myCell")
}
and Voila !
Create new file collection cell with XIB
In your "item at row for index" delegate method in view controller, initialize cell and return it.
Now any collection view within you view controller have the same cell
Create a Swift class(CollectionViewCell) by having UICollectionViewCell as a base class. Select create nib option while doing that.
Then create a view controller and add collection view to it in your storyboard and hook up this custom cell class in Identity Inspector. Basically, your structure needs to look like below
Related
I've read through all other people's questions and answers but still can't figure out what is wrong with my code and why on earth the cells are not displaying -
I am using Storyboard in my project, but chose to create the UICollectionView programmatically. The custom cells for the collectionView are xib files.
So I have a HomeViewController in which I have:
private var collectionView: UICollectionView = UICollectionView(
frame: .zero,
collectionViewLayout: UICollectionViewCompositionalLayout { sectionIndex, _ -> NSCollectionLayoutSection? in
return HomeViewController.createSectionLayout(section: sectionIndex)
}
)
and inside viewDidLoad I register the 3 custom xib cells I want to use in my collectionView:
collectionView.register(FeaturedPlaylistCollectionViewCell.self, forCellWithReuseIdentifier: Constants.Cells.FEATURED_PLAYLIST)
collectionView.register(NewReleasesCollectionViewCell.self, forCellWithReuseIdentifier: Constants.Cells.NEW_RELEASES)
collectionView.register(RecommendedTrackCollectionViewCell.self, forCellWithReuseIdentifier: Constants.Cells.RECOMMENDATION)
collectionView.dataSource = self
collectionView.delegate = self
view.addSubview(collectionView)
Each of the cells has a xib file with an identifier and a matching custom class with the same name as the xib file.
Inside the cellForItemAt I decide which cell I want to display by:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let type = sections[indexPath.section] // I identify properly which cell I want
switch type {
case .newReleases(let viewModels):
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: Constants.Cells.NEW_RELEASES, for: indexPath) as? NewReleasesCollectionViewCell else {
return UICollectionViewCell()
}
return cell
case .featuredPlaylists(let viewModels):
// same code, different identifier and different as? collectionViewCell
case .recommendedTracks(let viewModels):
// same code, different identifier and different as? collectionViewCell
}
}
Even though I added some labels and an image to my NewReleasesCollectionViewCell, for some reason the cells are not displaying and I just can't figure out why -.-
Any help would be appreciated
Edit:
For example my NewReleasesCollectionViewCell has the identifier "newReleases_Cell" saved into the Constants file and has the custom class called "NewReleasesCollectionViewCell". I do not have any code added yet to this file, just added some labels with text and in imageview in the xib itself
The problem is that these lines:
collectionView.register(FeaturedPlaylistCollectionViewCell.self, forCellWithReuseIdentifier: Constants.Cells.FEATURED_PLAYLIST)
... all tell the runtime to make a new cell instance based on the name of the class. But what you want is for the runtime to fetch the existing cell instance from a xib file.
For that, you would use a different method:
https://developer.apple.com/documentation/uikit/uicollectionview/1618083-register
Notice that the first parameter here is a UINib object, not a class.
I've been successfully nesting collections views into tableview for a while now.
What I still don't know how to do, is to do it while respecting the MVC pattern?
Right now I declare my tableview and its cells and in the cell (where the collectionView sits), I attach my collectionView (I got 1 per cell) and do the data mapping. It works, but it's spaghetti code where my View is acting like a controller.
I tried a few times to respect the MVC patterns. I can get my controller to control both my tableview and my collection. Where I struggle is to tell my collection View delegate which data it should pick as all I have as reference is the indexPath (of the collectionView) but not in which tableView that specific collectionView sits.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath) as! CollectionViewCell
return cell
}
The delegate only gives me the indexPath of the cell not which collectionView it is. To use a concrete example - let's assume that my tableview cells represents messages and that each message has a collectionView that controls reactions (like Discord). How do I tell my collectionView delegate which Message it is linked to?
Thanks a lot for the help!
This is very simple. Every view and its subclass has a property tag. You must have an IBOutlet or a simple reference to the CollectionView that sits in the TableViewCell. When you dequeue the TableViewCell just set the tag of your CollectionView equal to the indexPath.row of your tableViewCell like this:
tableViewCell.collectionView.tag = indexPath.row
Then in the UICollectionViewDataSource or UICollectionViewDelegate methods you may find which collectionView it is in other words which tableViewCell does this collectionView sits in. Here's an example:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath) as! CollectionViewCell
let dataObject = dataSourceArray[indexPath.row] as! YourDataModelObject
// Here you may set any property of the collectionView cell
return cell
}
P.S: It is not necessary to respect MVC to that extent. You may manage your CollectionView from the TableViewCell. Have a look at this example.
I want to do something similar to this where A, B and C are all different UICollectionViewController. My current approach is manually duplicating the cell in storyboard to all different UICollectionViews but have they all have same custom subclass. However, it's not DRY for UI development and I have to manually change replicate the changes in each UICollectionViewCell copy.
I know XIBs can be used to achieve this, but I'm stuck at how to link it with all UICollectionViewController through storyboard or minimal Swift code.
#Anushik use below steps to create custom cell using xib and reuse
1.Create UICollectionViewCell subclass along with xib
2.Add reuse identifier to the cell
3.Register the nib using register(nib:forCellWithReuseIdentifier) method in viewDidLoad method in UIViewController class
let nib = UINib(nibName: "CustomCollectionViewCell", bundle: nil)
collectionView.register(nib, forCellWithReuseIdentifier: "CustomCell")
4.Use the custom cell in cellForItemAt method of UICollectionViewDatasource
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCell", for: indexPath) as! CustomCollectionViewCell
let item = items[indexPath.row]
cell.configureCell(item: item)
return cell
}
I'm working with an UICollectionView. As dequeueReusableCell(withReuseIdentifier:for:) expects You must register a class or nib file using the register(_:forCellWithReuseIdentifier:) method before calling this method, I added a line in my viewDidLoad func as
self.collectionView!.register(PhotoCollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
Now when I'm using the cell for dequeuing and configuring, I'm getting error and app crashes.
fatal error: unexpectedly found nil while unwrapping an Optional value
This is my DataSource method:
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier,
for: indexPath) as! PhotoCollectionViewCell
let aPhoto = photoForIndexPath(indexPath: indexPath)
cell.backgroundColor = UIColor.white
cell.imageView.image = aPhoto.thumbnail //this is the line causing error
return cell
}
And this is my PhotoCollectionViewCell class
class PhotoCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var imageView: UIImageView! //I double checked this IBOutlet whether it connects with Storyboard or not
}
Original question
Now comes the interesting part.
I'm using a prototype cell in the UICollectionView and I set a reusable identifier from attributes inspector. Also I changed the custom class from identity inspector to my PhotoCollectionViewCell.
I searched for the same issue and found out that when using prototype cell, deleting
self.collectionView!.register(PhotoCollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
from code will work. I gave it a try and it works.
But I'm curious to know the reason behind this issue. I couldn't reproduce the same issue with UITableView but with UICollectionView.
Not a possible duplicate:
This UICollectionView's cell registerClass in Swift is about how to register class in UICollectionView. But my question doesn't expect how to register. My question is about an issue that isn't true with UITableView class but with UICollectionView only. I'm expecting the actual difference between this conflicting issue.
There are 3 ways to register a cell (either for UITableView or UICollectionView).
You can register a class. That means the cell has no nib or storyboard. No UI will be loaded and no outlets connected, only the class initializer is called automatically.
You can register a UINib (that is, a xib file). In that case the cell UI is loaded from the xib and outlets are connected.
You can create a prototype cell in the storyboard. In that case the cell is registered automatically for that specific UITableViewController or UICollectionViewController. The cell is not accessible in any other controller.
The options cannot be combined. If you have a cell prototype in the storyboard, you don't have to register again and if you do, you will break the storyboard connection.
You can assign Nib to Collection view cell with an identifier as follows :
self.collectionView.register(UINib(nibName: "nibName", bundle: nil), forCellWithReuseIdentifier: "cell")
Hope it helps.
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;
}