i ma making Swift app with Xcode and using CollectionViewCell to fetch data from my WordPress Website. i have 2 cells in my CollectionView , one for loading posts and second for Google AdMob , I want to show Ads after 4 posts which is working great but now the problem is when Second cell is loaded i mean AdMob cell loaded then the post of that number is hidden behind it , like if there are posts of number 1,2,3,4,5 and ads load after 4 posts then number 5 post is no where.. this is my code
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if (indexPath.item % 5 == 1){
let adcell = collectionView.dequeueReusableCell(withReuseIdentifier: "adcell", for: indexPath) as! MovieCollectionViewCell
// Google Ads Here
return adcell
}
else{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MovieCollectionViewCell", for: indexPath) as! MovieCollectionViewCell
cell.setup(with: newsData[indexPath.row])
return cell
}
}
please help me to solve this , i want to load ads but when ad loaded the post of that number should be loaded in next item cell.. thanks
First of all you use indexPath.item in the if block and indexPath.row in the else block, which might be a bit confusing but this is not the problem here.
indexPath.item is 4 on your fifth cell, so the if condition resolves to true and you return the adcell. On your sixth cell, indexPath.row is now 5, so the fifth item of newsData with index 4 is simply skipped.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if (indexPath.item % 5 == 4){
let adcell = collectionView.dequeueReusableCell(withReuseIdentifier: "adcell", for: indexPath) as! MovieCollectionViewCell
// Google Ads Here
return adcell
}
else {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MovieCollectionViewCell", for: indexPath) as! MovieCollectionViewCell
cell.setup(with: newsData[indexPath.item-(indexPath.item/5])
return cell
}
}
EDIT:
If not already done:
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return newsData.count + (newsData.count/5)
}
Related
I have a UICollectionView that I want to make 3 custom cells appear.
I have read the documentation but I haven't been able to fix this issue.
Is there something I am missing?
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 1
}
I have tried changing the return 1 to 3 to make 3 custom cells appear but it only makes the 1st custom cell appear 3 times.
I have created a video and linked the video below explaining my situation.
https://www.loom.com/share/9b5802d6cc7b4f9a93c55b4cf7d435bb
Edit I have used #Asad Farooq method and it seems to have worked for me. I added my CollectionView's shown below and I can now make custom cells!
if(indexPath.item==0)
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "DailyCollectionViewCell", for: indexPath) as! DailyCollectionViewCell
return cell
}
if(indexPath.item==1)
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "WeeklyCollectionViewCell", for: indexPath) as! WeeklyCollectionViewCell
return cell
}
else {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MonthlyCollectionViewCell", for: indexPath) as! MonthlyCollectionViewCell
return cell
}
}
As we can see from the documentation of Apple,
You typically don’t create instances of this class yourself. Instead, you register your specific cell subclass (or a nib file containing a configured instance of your class) using a cell registration. When you want a new instance of your cell class, call the dequeueConfiguredReusableCell(using:for:item:) method of the collection view object to retrieve one.
We have to register the cell to the collectionView before using it, for example:
class CustomCollectionViewCell: UICollectionViewCell {
// my custom collection view cell
}
Then we gonna register it to the collection view:
class MyViewController: UIViewController {
...
override func viewDidLoad(){
super.viewDidLoad()
...
self.myCollectionView.dataSource = self
// register the cells, so the collectionView will "know" which cell you are referring to.
self.myCollectionView.register(UINib(nibName: "CustomCollectionViewCell", bundle: nil), forCellReuseIdentifier: "customReuseIdentifier")
// register all type of cell you wanted to show.
}
}
extension MyViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// return number of cell you wanted to show, based on your data model
return 3
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = routineCollectionView.dequeueReusableCell(withReuseIdentifier: "customReuseIdentifier", for: indexPath) as! CustomCollectionViewCell
// cast the cell as CustomCollectionViewCell to access any property you set inside the custom cell.
// dequeue cell by the reuseIdentifier, "explain" to the collectionView which cell you are talking about.
return cell
}
}
The above code snippet is just a brief example, but I hope that explain the idea.
If you got multiple type of custom cell, you'll have to create classes for them (sub-class of UICollectionViewCell), register them to your collectionView, and dequeue them in collectionView(cellForRowAt:).
There are plenty of tutorial on the internet, here share one of my favourite:
https://www.raywenderlich.com/9334-uicollectionview-tutorial-getting-started
Edit:
If you are using storyboard only to add your custom collectionViewCell, you don't need to register the cell again, the cell already existed in the collectionView (Sorry the above code is just my preference). Just set the class & identifier of the cell, and dequeue the cell using the identifier in collectionView(cellForRowAt:).
We have to register the three different custom cell to the collectionView before using it then inside this function add this code
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if(indexPath.item==0)
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell1", for: indexPath) as! cell1
return cell1
}
if(indexPath.item==1)
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell2", for: indexPath) as! cell2
return cell2
}
else
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell3", for: indexPath) as! cell3
return cell3
}
I have an odd problem with my UICollectionView. When I select the first cell in my Collection View the last cell is highlighted but does not show up in the array of selected index paths. Why is this happening?
Here is the code I am using to select and deselect the cells?
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// handle tap events
print("You selected cell #\(indexPath.item)!")
let cell = collectionView.cellForItem(at: indexPath)
cell?.layer.borderWidth = highlightedCellBorderWidth
cell?.layer.borderColor = UIColor.yellow.cgColor
selectedImages.append(imageArray[indexPath.item])
print(selectedImages)
print(collectionView.indexPathsForSelectedItems)
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)
cell?.layer.borderWidth = 0
let index = selectedImages.index(of: imageArray[indexPath.item])
selectedImages.remove(at: index!)
print(selectedImages)
print(collectionView.indexPathsForSelectedItems)
}
You don't show how your cell is initialized, but...
collectionView(_:didDeselectItemAt:) is only called when the user successfully deselects an item. It does not get called if you deselect an item programmatically. If you are not resetting your layer's borderWidth when initializing a cell, the non-zero width will carry forward when the cell is reused by the collection view.
I found this link on S.O. which was helpful. To solve the problem I simply added the following code to my func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {}.
Code...
if collectionView.indexPathsForSelectedItems?.contains(indexPath) == true {
print("This cell is selected \(indexPath.item)")
cell?.layer.borderWidth = 3
cell?.layer.borderColor = UIColor.yellow.cgColor
}else{
cell?.layer.borderWidth = 0
}
I have a horizontal collection view.When user click on a photo,a UIView appear to cover the photo.Only 1 photo will can be selected in 1 time.Means that,if a photo is selected,if I click on another photo,the previous photo will back to normal.
I implement this code below:
var previousSelectedIndexPath : IndexPath?
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if previousSelectedIndexPath != nil {
let previousCell = collectionView.cellForItem(at: previousSelectedIndexPath!) as! photoCell
previousCell.uiViewHeight.constant = 0
}
let currentCell = collectionView.cellForItem(at: indexPath) as! photoCell
currentCell.uiViewHeight.constant = 250
previousSelectedIndexPath = indexPath
}
What I get is,when the collection view first launch,it shown the behavior I want.But when I click on a photo,then scroll to left,then click a new photo again,it show error
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an
Optional value
I think is the photo selected before is not appear on screen anymore,therefore it cant be update the constraints of the UIView of that photo.
I tried to implement this solution as well,but get the same error as well :
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath) as! photoCell
cell.uiViewHeight.constant = 250
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath) as! photoCell
cell.uiViewHeight.constant = 0
}
So in this case,how can I solve this problem?
Ken, don't force cast your UICollectionViewCell, your app could be crash.
Use safe code like that :
if let currentCell = collectionView.cellForItem(at: indexPath) as? photoCell {
// if succeed, do code
}
I have inserted a collectionView inside my tableViewCell. Tableview contains the list of categories and the collectionView contains all the product. How can I have a different number of items in the collectionView based off of which table view row was selected? I've tried storing the selected table view row and using that to define the number of items to be returned however it either crashes with no error code, tells me the value is nil or just does not display any clitems in the collectionView. Any help would be greatly appreciated. Thank you for your time.
Below is my code:
My Custom table view cell:
extension ExpandableCell: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
let toReturn = categoryItems.count
return counter
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
//
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionCell", for: indexPath) as! CustomCollectionViewCell
//What is this CustomCollectionCell? Create a CustomCell with SubClass of UICollectionViewCell
//Load images w.r.t IndexPath
print(self.selectedCategory.description)
let newArray = starbucksMenu[selectedCategory]
//cell.image.image = UIImage(named: (allItems[selectedCategory]?[indexPath.row])!)
cell.label.text = categoryItems[indexPath.row]
//cell.layer.borderWidth = 0.1
return cell
}
My table view delegate method:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print(indexPath.row)
word = indexPath.row
guard let cell = tableView.cellForRow(at: indexPath) as? ExpandableCell
else { return }
switch cell.isExpanded
{
case true:
self.expandedRows.remove(indexPath.row)
self.selectedCategory = ""
case false:
self.expandedRows.insert(indexPath.row)
}
self.selectedCategory = categories[indexPath.row]
print(self.selectedCategory)
//self.array = starbucksMenu[starbucksMenuCategories[indexPath.row]]!
//self.collectionView.reloadData()
cell.menuItems = allItems[selectedCategory]!
cell.categoryItems = allItems[selectedCategory]!
cell.isExpanded = !cell.isExpanded
self.itemsArray = allItems[selectedCategory]!
self.tableView.beginUpdates()
self.tableView.endUpdates()
}
I've tried many things, I've tried adding the items in an array and returning the count (displays nothing). I have a dictionary with the necessary items so I've also tried returning allItems[selectedCategory]?.count and this always returns an error, I believe selectedCategory has no value once this is called.
Make a for loop of collection view item with appropriate operation Between beginUpdate() and endUpdate()
I'm trying to implement a custom cell with support for user tapping. Previously the functions related are:
func collectionView(collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueResuableCell(withReuseIdentifier: "CustomCell", for: indexPath) as! CustomCell
cell.setEmpty() // to init the cell
return cell
}
func collectionView(collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath) as ! CustomCell
//implementations
self.collectionView.reloadItem(at: [indexPath])
}
Then I noticed that after the tapping, the second function gets called first, but the first one also gets called afterwards, which means after tapping my cell will still be set to empty, so I changed the first function to this:
let cell = collectionView.dequeueResuableCell(withReuseIdentifier: "CustomCell", for: indexPath) as! CustomCell
if cell.valueInside == nil {
cell.setEmpty() // this will set valueInside to be a non-nil value
}
return cell
But it's still not working properly. I tracked the process: when loading the UI for the first time, init cell first (with the setEmpty() method); then after the tapping, cell is updated, and then the first function is called, but the cell obtained by this
collectionView.dequeueResuableCell(withReuseIdentifier: "CustomCell", for: indexPath) as! CustomCell
shows that the value inside is still nil, so the cell is not really up-to-date. How should I fix this? Or is my implementation logical (should I init the cell somewhere else instead of using this
check if it's nil -> then init
logic)?
func collectionView(collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
is called every time your inquire for a cell like
collectionView.cellForItem(at: indexPath)
or
self.collectionView.reloadItem(at: [indexPath])
The best way to use is to declare a class level variable like an array to hold the backed data, then for cell creating get data from that array and in your didSelectItemAt update that array and then just force the collection view to update that cell only.
// In your class scope
private var arData: [String] = ["One", "Two", "Three"] // This could be populated in viewDidLoad as well
// .... rest of your code
func collectionView(collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueResuableCell(withReuseIdentifier: "CustomCell", for: indexPath) as! CustomCell
//...
if indexPath.row < arData.count {
cell.valueInside = arData[indexPath.row]
} else {
cell.valueInside = "Default Value"
}
return cell
}
// ....
func collectionView(collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
//implementations
if indexPath.row < arData.count {
arData[indexPath.row] = "newValue"
self.collectionView.reloadItem(at: [indexPath])
}
}
This is the logic on how to correctly change a cell view content now if you want to change it's appearance again you should set the flag or whatever status distinguishing mechanism you have in the didSelectItemAt and just reload the cell considering cellForItemAt will refer to that status and apply's that appearance change.