I migrated this project (an iMessage app with a collection view to hold stickers) from Swift 3 to Swift 5, and everything works but the header view function simply will not fire, nor will header show up.
I register the header cell in storyboard:
then in collection view functions:
private func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView {
let headerView: HeaderCollectionReusableView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "headerCell", for: indexPath as IndexPath) as! HeaderCollectionReusableView
print("CALLED")
//header1 = headerView as! UICollectionReusableView
return headerView
}
Ive even tried turning the header a different color in storyboard, however this function doesn't even print to console. What is wrong here? This used to work and the other cells work.
I migrated this project (an iMessage app with a collection view to hold stickers) from Swift 3 to Swift 5
Well you didn't migrate this line:
private func collectionView(_ collectionView: UICollectionView,
viewForSupplementaryElementOfKind kind: String,
atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView {
Change it to:
func collectionView(_ collectionView: UICollectionView,
viewForSupplementaryElementOfKind kind: String,
at indexPath: IndexPath) -> UICollectionReusableView {
Not private. Not atIndexPath. Not NSIndexPath. Talk Swift, not Objective-C, and don't hide this method from Objective-C or it can't be called by Cocoa.
Plus make absolutely sure you are in a place where adoption of UICollectionViewDataSource is declared (you probably are, since you say other methods are being called okay).
Related
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.
I want to add headers which are present inside an array which is obtained from json but the headers only show the last element present not the rest
here is the code
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView
{
let sectionHeaderView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "sectionHeaderIdentifier", for: indexPath) as! SectionHeaderReusableView
for i in 0..<sectionNames.count
{
sectionHeaderView.sectionHeader.text = sectionNames[indexPath[i]]
}
return sectionHeaderView
}
please help thanks in advance!
In your Storyboard, select your collection view, select Accessories –
Section Header.
A section header will now appear in your Storyboard. Add a UILabel to
it.
Make a new file in your project of type UICollectionReusableView, and
call it SectionHeaderCollectionReusableView.
In the Storyboard, select the Section Header Collection Reusable View
and give it an Identifier, call it “SectionHeader“.
Then give it a Custom Class: “SectionHeaderCollectionReusableView“
In the SectionHeaderCollectionReusableView.swift file, make an
IBOutlet for the label you made and call it headerLabel.
Finally, in your UICollectionViewController code, add a function:
override func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> SectionHeaderCollectionReusableView
{
let header = collectionView.dequeueReusableSupplementaryViewOfKind(UICollectionElementKindSectionHeader, withReuseIdentifier: "SectionHeader", forIndexPath: indexPath) as! SectionHeaderCollectionReusableView
header.headerLabel.text = "YOUR_HEADER_TEXT"
//You can add elements to view instead of just a label
let view: UIView(frame: CGRect(0,0,200,100))
view.backgroundColor = .red
return header}
The for loop given inside will always iterate till last element, hence always last element will only show, according to the code posted.
So please try removing the for loop, and load only sectionNames at current index.
I have more than one collectionView in a ViewController. The cell of those collectionViews has the same format.. so I'm reusing them. So my question is: How to identify in the method
func collectionView(_ collectionView: UICollectionView,
didSelectItemAt indexPath: IndexPath)
I don't want to do a couple of if's
I've found this solution everywhere, but really don't like it. Here is the code
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath)
if let aCell = cell as? ItemCollectionViewCell{
aCell.setupCell(with: self.items[indexPath.item])
}
return cell
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if collectionView == self.colletionViewTwo{
// goto viewController1
}else if collectionView == self.colletionViewOne{
// goto viewController2
}
}
Create two classes that implement the collection view delegate and data source and use one of each. So you'll have these two extra objects in your current view controller.
Seeing your code now, the above is probably too heavy. Alternatively, add a dictionary in which you store the collection view as key and a selector as value. This is extensible as you say you want.
To be honest, what's your issue an if (or switch) statement like you have now?
What is the most common way to delete cells from a UICollectionView?
In a UITableView I use the editActionsForRowAt or editingStyle methods to delete rows, does UIControllerView has something similar or you need to implement your own deleting method?
What I have is a UICollectionView with a lot of photos where each cell/photo is attached to a segue which takes you to a larger version of the photo.
The easiest way I could be to do it in the didSelectItemAt method but in my case that is not an option since as soon as a photo is tapped it segues to the other viewController (larger image).
What would be the best way to add a deleting functionality in a situation like the one I'm describing above?
The following threads show how to delete using the didSelectItemAt.
How to add a delete button to Collection View Cell in Swift?
How to delete item from collection view?
CODE
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return fruits.count
}
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "fruitCustomCell", for: indexPath) as! fruitCollectionViewCell
cell.labelFruitName.text = fruits[indexPath.row].fruitName
return cell
}
App Diagram
I have implemented a UICollectionView that holds list of UIImageView objects.
I want the user to be taken to YouTube with specific URL when he touched an image.
But I don't know how to add touch listener for each UICollectionViewCell:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
var cell: PhotoCell = collectionView.dequeueReusableCellWithReuseIdentifier("PhotoCell", forIndexPath: indexPath) as PhotoCell
cell.loadImage(thumbnailFileURLs[indexPath.row], originalImagePath: originalFileURLs[indexPath.row])
return cell
}
My PhotoCell class has a member variable that holds the URL to youtube.
For each PhotoCell object, when pressed, I want my app to send the user to youtube.com website or APP (if installed)
You should implement UICollectionViewDelegate protocol method collectionView(_:didSelectItemAtIndexPath:). When you press one of your collection view cells this method get called. Here is sample implementation
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let url = thumbnailFileURLS[indexPath.item]
if UIApplication.sharedApplication().canOpenURL(url) {
UIApplication.sharedApplication().openURL(url)
}
}
By the way I don't know where you get url. So I improvised a bit :)
Swift 5
didSelectItemAtIndexPath has been renamed to didSelectItemAt in Swift 5
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
//Do your logic here
}