Check Boxes in CollectionView are changing positions when scrolling - ios

I have collectionView (3*3) with Images I am loading from server and I placed a checkBox in the top left corner of each cell so that I can select the cells and based on the selected cells I will get ids for the respective cells images(ids coming from server) and I am able do everything right. But, the problem is if there is are 20 images and if I check the 5 random cells which are loaded for the first time and when I scroll down to select other cells 5 other random checkBoxes are already checked and if I scroll up again some other 5 random cells are checked. It appears that the checked checkBoxes are changing positions because of the dequeue reusable property in the cellForItemAtIndexPath of UICollectionView DataSource method..
I have no Idea how to overcome this problem. Please help me If any one knows how to do this. I am posting below the code I wrote so far and some simulator screenshots for better understanding of the problem...
EditCertificatesViewController:
import UIKit
import Alamofire
protocol CheckBoxState {
func saveCheckBoxState(cell: EditCertificateCell)
}
class EditCertificatesViewController: UIViewController,UICollectionViewDelegate,UICollectionViewDataSource,UICollectionViewDelegateFlowLayout {
#IBOutlet weak var certificatesCollectionView: UICollectionView!
var certificatesArray = [Certificates]()
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Delete Certificates"
navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return certificatesArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "editCertificate", for: indexPath) as! EditCertificateCell
if let certificateURL = URL(string: certificatesArray[indexPath.item].imagePath) {
cell.certificateImage.af_setImage(withURL: certificateURL)
}
cell.certificateId.text = "\(certificatesArray[indexPath.item].imageId)"
cell.selectCertificate.customBox()
if selectedCellIndex.contains(indexPath.item) {
cell.selectCertificate.on = true
}
else {
cell.selectCertificate.on = false
}
cell.selectCertificate.tag = indexPath.item
cell.checkState = self
return cell
}
}
extension EditCertificatesViewController: CheckBoxState {
func saveCheckBoxState(cell: EditCertificateCell) {
if cell.selectCertificate.on == true {
cell.selectCertificate.on = false
}
else {
cell.selectCertificate.on = true
}
if selectedCellIndex.contains(cell.selectCertificate.tag) {
selectedCellIndex = selectedCellIndex.filter{$0 != cell.selectCertificate.tag}
}
else {
selectedCellIndex.append(cell.selectCertificate.tag)
}
print("Status1 \(selectedCellIndex.sorted { $0 < $1 })")
// certificatesCollectionView.reloadData()
}
}
EditCertificateCell:
import UIKit
class EditCertificateCell: UICollectionViewCell {
#IBOutlet weak var certificateImage: UIImageView!
#IBOutlet weak var selectCertificate: BEMCheckBox!
#IBOutlet weak var certificateId: UILabel!
#IBOutlet weak var selectCertificateBtn: UIButton!
var checkState: CheckBoxState?
override func awakeFromNib() {
super.awakeFromNib()
self.selectCertificateBtn.addTarget(self, action: #selector(btnTapped(_:event:)), for: .touchUpInside)
}
#objc func btnTapped(_ sender: UIButton,event: UIEvent) {
self.checkState?.saveCheckBoxState(cell: self)
}
}

CollectionView dequeue's your cell. To rid of this you need to maintain array of selected certificates. Follow below procedure.
Create an array arrSelectedIndex : [Int] = []
In cellForRow,
First check either current index in available in arrSelectedIndex or not? If yes, then make your cell as selected otherwise keep it uncheck.
Give tag to your check button as like this buttonCheck.tag = indexPath.item
If you wanted to select images on check button action, do below.
Get the button tag let aTag = sender.tag
Now check wther this index is available in arrSelectedIndex or not? If yes then remove that index from from the arrSelectedIndex otherwise append that array.
reload your cell now.
If you wanted to select images on didSelectItem instaead check button action, do below.
Now check wther this selected index (indexPath.item) is available in arrSelectedIndex or not? If yes then remove that index from from the arrSelectedIndex otherwise append that array.
reload your cell now.
As this procedure is lengthy so I can only explain you how to do this. If need further help then you can ask.

This is expected. Because you are reusing the cells.
Consider this. You select the first 2 cells, and now scroll down. This function of yours will be called func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {. Now this might get the views from the first 2 cells, that you had selected, and their checkboxes are already selected too.
You need to unset them, and set them, depending upon their last state.
I would recommend adding another property isCertificateSelected to your Certificate model. Each time the user taps on a cell, you retrieve the model, and set/unset this bool. When collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) is called, you retrieve the isCertificateSelected again, and set the checkbox accordingly.

Create an array var Status1CheckList = [Int]()
And in cellForItemAt indexPath check the condition like
if Status1CheckList.contains(indexPath.row) {
cellOfCollection.CheckBtn.setImage(UIImage(named: "check"), for: .normal)
} else {
cellOfCollection.CheckBtn.setImage(UIImage(named: "uncheck"), for: .normal)
}
cellOfCollection.CheckBtn.tag = indexPath.row
cellOfCollection.CheckBtn.addTarget(self, action: #selector(self.checkList), for: .touchUpInside)
And checklist method, After selecting button reload the collectionview
#objc func checkList(_ sender: UIButton) {
if Status1CheckList.contains(sender.tag) {
Status1CheckList = Status1CheckList.filter{ $0 != sender.tag}
} else {
Status1CheckList.append(sender.tag)
}
print("Status1 \(Status1CheckList.sorted { $0 < $1 })")
self.collectionviewObj.reloadData()
}

Related

Variables in UICollectionViewCells change to different cells

I have a shop menu for my game in swift. I am using a UICollectionView to hold the views for the items. There is a black glass covering over them before they are bought, and it turns clear when they buy it. I am storing data for owning the certain items in the cells' classes. When I scroll down in the scrollView and then come back up after clicking a cell. A different cell than collected has the clear class and the one I previously selected is black again.
import UIKit
class Shop1CollectionViewCell: UICollectionViewCell {
var owned = Bool(false)
var price = Int()
var texture = String()
#IBOutlet weak var glass: UIImageView!
#IBOutlet weak var ball: UIImageView!
func initiate(texture: String, price: Int){//called to set up the cell
ball.image = UIImage(named: texture)
if owned{//change the glass color if it is owned or not
glass.image = UIImage(named: "glass")
}else{
glass.image = UIImage(named: "test")
}
}
func clickedOn(){
owned = true//when selected, change the glass color
glass.image = UIImage(named: "glass")
}
}
Then I have the UICollectionView class
import UIKit
class ShopViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
struct ball{
var price = Int()
var texture = String()
var owned = Bool()
}
var balls = Array<ball>()//This is assigned values, just taken off of the code because it is really long
override func viewDidLoad() {
super.viewDidLoad()
balls = makeBalls(
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return (balls.count - 1)
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! Shop1CollectionViewCell
cell.initiate(texture: balls[indexPath.item].texture, price: 1)
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath) as! Shop1CollectionViewCell
cell.clickedOn()
}
}
I am wondering why the variables stored in one class of a cell are being switched to another.
Please ask if you need me to add any additional information or code.
You should definitely implement prepareForReuse, and set the cell back to its default state - which in this case would be with the black glass, and unowned.
You can still change the glass in the same way as you're doing in didSelectItemAt, but I'd recommend having the view controller track the state of each ball.
For example, when didSelectItemAt gets called - the view controller would update the ball stored in self.balls[indexPath.row] to have owned = true.
That way, the next time cellForItemAt gets called, you'll know what colour the glass should be by checking the value in self.balls[indexPath.row].owned.
Lastly, you're returning balls.count - 1 in numberOfItems, is this intentional?
In the case where have 10 balls, you're only going to have 9 cells. If you want a cell for each of your objects you should always return the count as is.
Sometimes, you may have to override the following function
override func prepareForReuse(){
super.prepareForReuse()
// reset to default value.
}
in class Shop1CollectionViewCell to guarantee cells will behave correctly. Hope you got it.

How to convert `Observable` to `ControlEvent`

We have a view controller MainCollectionView that contains a collection view with a number of cells FooCell. And inside each FooCell, there is a collection view and a list of cells BarCell.
How do I propagate a button tapped event in the BarCell to MainCollectionView?
This is what we have:
class FooCell: ... {
private let barCellButtonTappedSubject: PublishSubject<Void> = PublishSubject<Void>()
var barCellButtonTappedObservable: Observable<Void> {
return barCellButtonTappedSubject.asObserver()
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeue(...)
if let cell = cell as BarCell {
cell.button.rx.tap.bind { [weak self] in
self?.barCellButtonTappedSubject.onNext(())
}.disposed(by: cell.rx.reusableDisposeBag)
}
return cell
}
}
class MainCollectionView: ... {
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeue(...)
if let cell = cell as FooCell {
cell.barCellButtonTappedObservable.subscribe { [weak self] in
// Do other stuff when the button inside bar cell is tapped.
}.disposed(by: cell.rx.reusableDisposeBag)
}
return cell
}
}
This works until I read about ControlEvent:
it never fails
it won't send any initial value on subscription
it will Complete sequence on control being deallocated
it never errors out
it delivers events on MainScheduler.instance
It looks like it is more appropriate to use ControlEvent in the FooCell:
private let barCellButtonTappedSubject: PublishSubject<Void> = PublishSubject<Void>()
var barCellButtonTappedObservable: Observable<Void> {
return barCellButtonTappedSubject.asObserver()
}
What is the right way to convert this barCellButtonTappedObservable to a ControlEvent? Or is there other better idea to propagate the ControlEvent in the nested cell to the outer view controller?
I personally prefer using RxAction for this kind of stuff, but because you have already declared a PublishSubject<Void> in your cell, this is how you can convert a subject to ControlEvent
controlEvent = ControlEvent<Void>(events: barCellButtonTappedSubject.asObservable())
As straight forward as it can get! but if thats all you wanna do, you don't even need a barCellButtonTappedSubject
controlEvent = ControlEvent<Void>(events: cell.button.rx.tap)
In fact, you don't even need to declare a control event :) because cell.button.rx.tap itself is a control event :) So if you declare your button as public property in your cell, you can directly access its tap control event in your tableView controller
But personally, I would use RxAction rather than declaring a publishSubject or controlEvent your FooCell can ask for action from your TableViewController
class FooCell: ... {
var cellTapAction : CocoaAction! = nil
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeue(...)
if let cell = cell as BarCell {
cell.button.rx.action = cellTapAction
}
return cell
}
}
Finally your TableViewController/CollectionViewController can pass action as
class MainCollectionView: ... {
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeue(...)
if var cell = cell as FooCell {
cell.cellTapAction = CocoaAction { _ -> Observable<Void> in
debugPrint("button in cell tapped")
return Observable.just(())
}
}
return cell
}
}
Only thing you would have to handle is if cellctionView is embedded inside FooCell because am passing action after deQueReusableCell embedded collectionView might load even before action is passed to it so you will have to tweak the logic to either reload the embedded collection view after action passed to FooCell or any other workaround which will solve this issue :)
Hope it helps :) I believe using Action makes code cleaner and easy to understand.
I've never used Actions which the other answers have mentioned. I also wonder why you seem to be manually setting up your delegate instead of using RxCocoa to do it. Lastly, it feels like you probably want some way of knowing which button was tapped. I do that in the code below by assigning each Bar cell an ID integer.
class BarCell: UICollectionViewCell {
#IBOutlet weak var button: UIButton!
func configure(with viewModel: BarViewModel) {
button.rx.tap
.map { viewModel.id }
.bind(to: viewModel.buttonTap)
.disposed(by: bag)
}
override func prepareForReuse() {
super.prepareForReuse()
bag = DisposeBag()
}
private var bag = DisposeBag()
}
class FooCell: UICollectionViewCell {
#IBOutlet weak var collectionView: UICollectionView!
func configure(with viewModel: FooViewModel) {
viewModel.bars
.bind(to: collectionView.rx.items(cellIdentifier: "Bar", cellType: BarCell.self)) { index, element, cell in
cell.configure(with: element)
}
.disposed(by: bag)
}
override func prepareForReuse() {
super.prepareForReuse()
bag = DisposeBag()
}
private var bag = DisposeBag()
}
class MainCollectionView: UIViewController {
#IBOutlet weak var collectionView: UICollectionView!
var viewModel: MainViewModel!
override func viewDidLoad() {
super.viewDidLoad()
let foos = viewModel.foos
.share()
let buttonTaps = foos
.flatMap { Observable.merge($0.map { $0.bars }) }
.flatMap { Observable.merge($0.map { $0.buttonTap.asObservable() }) }
buttonTaps
.subscribe(onNext: {
print("button \($0) was tapped.")
})
.disposed(by: bag)
foos
.bind(to: collectionView.rx.items(cellIdentifier: "Foo", cellType: FooCell.self)) { index, element, cell in
cell.configure(with: element)
}
.disposed(by: bag)
}
private let bag = DisposeBag()
}
struct FooViewModel {
let bars: Observable<[BarViewModel]>
}
struct BarViewModel {
let id: Int
let buttonTap = PublishSubject<Int>()
}
struct MainViewModel {
let foos: Observable<[FooViewModel]>
}
The most interesting bit about the code was the merging up of all the buttonTaps. That was a bit of an adventure to figure out. :-)
I would personally add an action to the button before trying to observe its state like that and handle bubbling up your response from there.
class Cell: UICollectionViewCell {
let button = UIButton(type: .custom)
func setUpButton() {
button.addTarget(self, action: #selector(Cell.buttonTapped), for: .touchUpInside)
}
#IBAction func buttonTapped(sender: UIButton) {
//This runs every time the button is tapped
//Do something here to notify the parent that your button was selected or handle it in this Cell.
print(sender.state)
}
}

How to access off-screen but existing cells in UICollectionView in Swift?

The title might be a little hard to understand, but this situation might help you with it.
I'm writing a multiple image picker. Say the limit is 3 pictures, after the user has selected 3, all other images will have alpha = 0.3 to indicate that this image is not selectable. (Scroll all the way down to see a demo)
First of all, this is the code I have:
PickerPhotoCell (a custom collection view cell):
class PickerPhotoCell: UICollectionViewCell {
#IBOutlet weak var imageView: UIImageView!
var selectable: Bool {
didSet {
self.alpha = selectable ? 1 : 0.3
}
}
}
PhotoPickerViewController:
class PhotoPickerViewController: UICollectionViewController {
...
var photos: [PHAsset]() // Holds all photo assets
var selected: [PHAsset]() // Holds all selected photos
var limit: Int = 3
override func viewDidLoad() {
super.viewDidLoad()
// Suppose I have a func that grabs all photos from photo library
photos = grabAllPhotos()
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell ...
let asset = photos[indexPath.row]
...
// An image is selectable if:
// 1. It's already selected, then user can deselect it, or
// 2. Number of selected images are < limit
cell.selectable = cell.isSelected || selected.count < limit
return cell
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath) as! PickerPhotoCell
if cell.isSelected {
// Remove the corresponding PHAsset in 'selected' array
} else {
// Append the corresponding PhAsset to 'selected' array
}
// Since an image is selected/deselected, I need to update
// which images are selectable/unselectable now
for visibleCell in collectionView.visibleCells {
let visiblePhoto = visibleCell as! PickerPhotoCell
visiblePhoto.selectable = visiblePhoto.isSelected || selected.count < limit
}
}
}
This works almost perfectly, except for one thing, look at the GIF:
The problem is
After I've selected 3 photos, all other visible photos have alpha = 0.3, but when I scroll down a little more, there are some photos that still have alpha = 1. I know why this is happening - Because they were off-screen, calling collectionView.visibleCells wouldn't affect them & unlike other non-existing cells, they did exist even though they were off-screen. So I wonder how I could access them and therefore make them unselectable?
The problem is that you are trying to store your state in the cell itself, by doing this: if cell.isSelected.... There are no off screen cells in the collection view, it reuses cells all the time, and you should actually reset cell's state in prepareForReuse method. Which means you need to store your data outside of the UICollectionViewCell.
What you can do is store selected IndexPath in your view controller's property, and use that data to mark your cells selected or not.
pseudocode:
class MyViewController {
var selectedIndexes = [IndexPath]()
func cellForItem(indexPath) {
cell.isSelected = selectedIndexes.contains(indexPath)
}
func didSelectCell(indexPath) {
if selectedIndexes.contains(indexPath) {
selectedIndexes.remove(indexPath)
} else if selectedIndexes.count < limiit {
selectedIndexes.append(indexPath)
}
}
}

How to change image of imageview in tableview custom cell with pagination.

I am using a tableview in an app in which I have used pagination. The request is sent to the server and it returns items in batches of size 10. everything is working fine till now. Now I have an imageview in my tableview cells (custom). I want that when the image of that imageview toggles when user taps on it. I tried this thing in the following way:
TableviewController:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell : AdventureTableViewCell = tableView.dequeueReusableCell(withIdentifier: "adventureCell" , for: indexPath) as? AdventureTableViewCell else {
fatalError("The dequeued cell is not an instance of AdventureViewCell.")
}
cell.adventureName.text = adventureList[indexPath.row]
cell.amountLabel.text = "\(adventurePriceList[indexPath.row])$"
cell.favouriteButtonHandler = {()-> Void in
if(cell.favouriteButton.image(for: .normal) == #imageLiteral(resourceName: "UnselectedFavIcon"))
{
cell.favouriteButton.setImage(#imageLiteral(resourceName: "FavSelectedBtnTabBar"), for: .normal)
}
else
{
cell.favouriteButton.setImage(#imageLiteral(resourceName: "UnselectedFavIcon"), for: .normal)
}
}
}
CustomCell:
class AdventureTableViewCell: UITableViewCell {
#IBOutlet weak var adventureName: UILabel!
#IBOutlet weak var adventureImage: UIImageView!
#IBOutlet weak var amountLabel: UILabel!
#IBOutlet weak var favouriteButton: UIButton!
#IBOutlet weak var shareButton: UIButton!
var favouriteButtonHandler:(()-> Void)!
var shareButtonHandler:(()-> Void)!
override func awakeFromNib() {
super.awakeFromNib()
adventureName.lineBreakMode = .byWordWrapping
adventureName.numberOfLines = 0
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
override func prepareForReuse() {
adventureImage.af_cancelImageRequest()
adventureImage.layer.removeAllAnimations()
adventureImage.image = nil
}
#IBAction func favouriteButtonTapped(_ sender: Any) {
self.favouriteButtonHandler()
}
Now the problem which I am facing is that if user taps the first the imageview on any cell it changes its image, but along with that every 4th cell changes it image.
For example, if I have tapped imageview of first cell its image is changed but image of cell 5, 9, 13... also get changed.
What is wrong with my code? Did I miss anything? It is some problem with indexPath.row due to pagination, but i don't know what is it exactly and how to solve it. I found a similar question but its accepted solution didn't work for me, so any help would be appreciated.
if you need to toggle image and after scrolling also that should be in last toggle state means you need to use an array to store index position and toggle state by comparing index position and scroll state inside cellfoeRowAtIndex you can get the last toggle state that is one of the possible way to retain the last toggle index even when you scroll tableview otherwise you will lost your last toggle position
if self.toggleStatusArray[indexPath.row]["toggle"] as! String == "on"{
cell.favouriteButton.setImage(#imageLiteral(resourceName: "FavSelectedBtnTabBar"), for: .normal)
} else {
cell.favouriteButton.setImage(#imageLiteral(resourceName: "UnselectedFavIcon"), for: .normal)
}
cell.favouriteButtonHandler = {()-> Void in
if self.toggleStatusArray[indexPath.row]["toggle"] as! String == "on"{
//Assign Off status to particular index position in toggleStatusArray
} else {
//Assign on status to particular index position in toggleStatusArray
}
}
Hope this will help you
Your code looks OK, I see just one big error.
When u are setting dynamic data (names, images, stuff that changes all the time) use func tableView(UITableView, willDisplay: UITableViewCell, forRowAt: IndexPath) not cellForRowAt indexPath.
cellForRowAt indexPath should be used for static resources, and cell registration.
If u are on iOS 10 + take a look at prefetchDataSource gonna speed things up a loot, I love it.
https://developer.apple.com/documentation/uikit/uitableview/1771763-prefetchdatasource
Small example:
here u register the cell, and set up all the stuff that is common for all the cells in the table view
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "adventureCell" , for: indexPath)
cell.backgroundColor = .red
return cell
}
here adjust all the stuff that is object specific
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cell.nameLabel.text = model[indexPath.row].name
// also all the specific UI stuff goes here
if model[indexPath.row].age > 3 {
cell.nameLabel.textColor = .green
} else {
cell.nameLabel.textColor = .blue
}
}
You need this because cells get reused, and they have their own lifecycle, so you want to set specific data as late as possible, but you want to set the generic data as less as possible ( most of the stuff you can do once in cell init ).
Cell init is also a great place for generic data, but u can not put everything there
Also, great thing about cell willDisplay is the that u know actual size of the frame at that point

While fast scrolling collectionview the first row data duplicate in the last row

i wanted to make an application with a design that looks like the app store so i followed let's build that app tutorial on how to build it and the result was excellent, when i tested it on my device it seem that when i fast scroll the collection-view the data that's in the first row take place on the last row and when scroll fast up again, the data that supposed to be in the last row i found it in the first row and when i scroll left and right in the row when the cell go off the screen it re-update to the right data but when fast scroll up and down fast again the data between the first row and last row go crazy.
i added image to the case in the end of the code.i spent 5 days trying to fix this but no luck at all i tried alot of solution like reset the data in the cell to nil before reseting it and alot other u will find it in the code but no luck , I really appreciate any help you can provide, Thanks
*update
after #Joe Daniels answer all the data are staple and work fine except the images it still go crazy when fast scrolling but it return after 7/8 sec of stopping scrolling to the right image
first class that contain the vertical collectionview
i made the width of the cell equal the width of the view , i tried the cell.tag == index-path.row solution but it didn't work
class HomePageVC: UIViewController , UICollectionViewDataSource , UICollectionViewDelegate , UICollectionViewDelegateFlowLayout ,SWRevealViewControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
mainProductsRow.delegate = self
mainProductsRow.dataSource = self
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if let count = productCategory?.count {
return count + 1
}
return 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if indexPath.row == 0 {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: LargeHomeCategoriesCell.identifier, for: indexPath) as! LargeHomeCategoriesCell
cell.categoriesHomePageVC = self
cell.catIndexPath = indexPath.row
cell.productCategories = productCategory
cell.seeMore.addTarget(self, action: #selector(self.seeMoreCat(_:)), for: UIControlEvents.touchUpInside)
return cell
}
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "RowCell", for: indexPath) as! HomeCategoriesCell
cell.categoriesHomePageVC = self
cell.catIndexPath = indexPath.row
cell.seeMore.tag = (indexPath.row - 1 )
cell.seeMore.addTarget(self, action: #selector(self.seeMore(_:)), for: UIControlEvents.touchUpInside)
// cell.tag = indexPath.row - 1
cell.productCategory = nil
//if cell.tag == indexPath.row - 1{
cell.productCategory = productCategory?[indexPath.row - 1 ]
// }
return cell
}
**Second Class that contain the horizontal collectionview that is in the first collectionview cell **
in the cell-for-item-At-index-Path i printed the index-path.row when the view-load it printed 0 , 1 ,2 and didn't print the last index '3' and the same thing happen again when i scroll to the end of the collection view
class HomeCategoriesCell: UICollectionViewCell , UICollectionViewDataSource , UICollectionViewDelegateFlowLayout , UICollectionViewDelegate{
#IBOutlet weak var seeMore: UIButton!
#IBOutlet weak var categorytitle: UILabel!
#IBOutlet weak var productsCollectionView: UICollectionView!
let favFuncsClass = FavItemsFunctionality()
let onCartFuncsClass = OnCartFunctionality()
var productCategory : ProductCategories? {
didSet {
if let categoryTitle = productCategory?.name {
categorytitle.text = categoryTitle
}
}
override func awakeFromNib() {
recivedNotification()
productsCollectionView.delegate = self
productsCollectionView.dataSource = self
productsCollectionView.backgroundColor = UIColor.clear
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if let count = productCategory?.products?.count {
return count
}
return 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
print(indexPath.row)
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "HProductCell", for: indexPath) as! HomeProductCell
cell.tag = indexPath.row
cell.productImage.image = #imageLiteral(resourceName: "PlaceHolder")
cell.productTitle.text = nil
cell.productPrice.text = nil
cell.discountLabel.text = nil
cell.preDiscountedPrice.text = nil
cell.favButton.setImage(UIImage(named:"Heart_icon"), for: UIControlState.normal)
cell.addToCart.setImage(UIImage(named:"cart"), for: UIControlState.normal)
cell.configCell(products: nil)
if cell.tag == indexPath.row {
cell.configCell(products: productCategory?.products?[indexPath.item])
}
cell.catNum = indexPath.row
return cell
}
// Thanks to Joe Daniels i added this code and my nightmare become way less scary :P
override func prepareForReuse() {
super.prepareForReuse()
productsCollectionView.reloadData()
}
}
Product Cell
i tried the prepare-For-Reuse() and reseted all the data to nil but still didn't work
class HomeProductCell: UICollectionViewCell {
func configCell(products :productDetails?){
if let title = products?.name {
self.productTitle.text = title
}else { self.productTitle.text = nil}
if let price = products?.price {
self.productPrice.text = "\(price)"
}else { self.productPrice.text = nil }
}
override func prepareForReuse() {
super.prepareForReuse()
self.productImage.image = #imageLiteral(resourceName: "PlaceHolder")
productTitle.text = nil
productPrice.text = nil
discountLabel.text = nil
preDiscountedPrice.text = nil
favButton.setImage(UIImage(named:"Heart_icon"), for: UIControlState.normal)
addToCart.setImage(UIImage(named:"cart"), for: UIControlState.normal)
}
i took a screen shoots of the case you can find it here
i Found the Perfect Solution that worked for me as magic after 15 days of suffering xD i used this library (https://github.com/rs/SDWebImage).
To use it in swift put this line in your bridging Header
#import <SDWebImage/UIImageView+WebCache.h>
And in the collectionViewCell i used this line
self.productImage.sd_setImage(with: myUrl , placeholderImage: UIImage(named:"yourPlaceHolderImage")
The inner collectionView has no way of knowing that it went off screen. do a reload in prepareForReuse

Resources