CollectionView reloadData() weird behaviour - ios

I want to make a Collection View with cells, where you can select the colors of my little game. Therefor you can choose between different themes (each in one cell). If the theme is unlocked (by achieving scores), the blur view and the lock-icon should disappear, and all available cells/ themes should be selectable. I implemented this like so:
(Collection ViewController Class:)
import UIKit
import PMSuperButton
class myCollectionViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
#IBOutlet weak var backBtn: UIButton!
let items = ["Default dot", "Trap your first dot", "Score 15 or less (classic)", "Score 5 or less (classic)", "Score 15 or less (hard)", "Score 25 or less (no chill)", "Score 15 or less (snake)", "Score 15 or less everywhere", "Score 10 or less everywhere", "Circle the dot 100 times", "10"]
var preSelected = IndexPath()
#IBOutlet weak var myCollectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
updateUnlockedString()
updateColor()
}
func updateColor(){
view.backgroundColor = myColors().selected(value: selectedInstance.selectedColor).backgroundColor
}
func updateUnlockedString(){
let easyHS = UserDefaults.standard.integer(forKey: "highscoreeasy")
let classicHS = UserDefaults.standard.integer(forKey: "highscoreclassic")
let hardHS = UserDefaults.standard.integer(forKey: "highscorehard")
let nochillHS = UserDefaults.standard.integer(forKey: "highscoreno chill")
let snakeHS = UserDefaults.standard.integer(forKey: "highscoresnake")
var unlockedString = ""
//unimportant steps deleted
UserDefaults.standard.set(unlockedString, forKey: "unlocked")
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return items.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! myCollectionViewCell
if(unlockInstance.unlockedString.contains(String(indexPath.item))){
cell.unlockedEffect()
print("unlocked Effect")
}
cell.label.text = items[indexPath.item]
cell.removeHighlight()
if indexPath.item == selectedInstance.selectedColor{
cell.highlightEffect()
preSelected = IndexPath(item: indexPath.item, section: indexPath.section)
}
cell.setupCellFilling(playerColor: myColors().selected(value: indexPath.item).playerColor, objColor: myColors().selected(value: indexPath.item).objColor, defaultColor: myColors().selected(value: indexPath.item).defaultColor, backgroundColor: myColors().selected(value: indexPath.item).backgroundColor)
myCollectionView = collectionView
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
collectionView.allowsMultipleSelection = false
if ((collectionView.cellForItem(at: indexPath) as! myCollectionViewCell?) != nil) && unlockInstance.unlockedString.contains(String(indexPath.item)){
selectedInstance.selectedColor = indexPath.item
updateColor()
collectionView.reloadData()
}
}
#IBAction func backPressed(_ sender: Any) {
cameInstance.came = true
}
}
(Collection CELL Class:)
import UIKit
class myCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var label: UILabel!
#IBOutlet weak var view: UIView!
#IBOutlet weak var blurView: UIVisualEffectView!
#IBOutlet weak var lockedBlur: UIVisualEffectView!
#IBOutlet weak var lockedIcon: UIImageView!
override func layoutSubviews() {
// cell rounded section
self.layer.cornerRadius = 15.0
self.layer.masksToBounds = true
// cell shadow section
self.contentView.layer.cornerRadius = 15.0
self.contentView.layer.masksToBounds = true
self.layer.shadowColor = myColors().selected(value: selectedInstance.selectedColor).defaultColor.cgColor
self.layer.shadowOffset = CGSize(width: 0.0, height: 0.0)
self.layer.shadowRadius = 6.0
self.layer.shadowOpacity = 0.4
self.layer.cornerRadius = 15.0
self.layer.masksToBounds = false
self.layer.shadowPath = UIBezierPath(roundedRect: self.bounds, cornerRadius: self.contentView.layer.cornerRadius).cgPath
super.layoutSubviews()
}
extension myCollectionViewCell{
func highlightEffect(){
self.layer.borderWidth = 5.0
self.layer.borderColor = UIColor.systemGreen.cgColor
}
func removeHighlight(){
self.layer.borderColor = UIColor.clear.cgColor
}
func unlockedEffect(){
lockedBlur.alpha = 0
lockedIcon.alpha = 0
}
func setupCellFilling(playerColor: UIColor, objColor: UIColor, defaultColor: UIColor, backgroundColor: UIColor){
lockedBlur.effect = UIBlurEffect(style: .extraLight)
blurView.effect = UIBlurEffect(style: .systemUltraThinMaterialDark)
lockedIcon.tintColor = objColor
view.backgroundColor = backgroundColor
}
}
The problem
When I open the CollectionView (viewDidLoad), everything appears 100% fine! All cells that should be locked, appear locked (lockedBlur.alpha = 0 and lockedIcon.alpha = 0). Also, only the locked cells are selectable, which I can see with the green border. Only the selected Cell has a green border, thats right.
BUT: When I select a cell, some random locked cells loose their blur view and loose their lock-icon (so blockedBlur and lockedIcon disappear). They still aren't selectable. Sometimes this affects all cells, sometimes only e.g. 2.
When I select a cell, the didSelectItemAt Function gets called. And this also calls updateColors() which only changes the background and calls the mysterious reloadData(). In my opinion the reloadData() SOMEHOW makes the lockedBlur and lockedIcon disappear, and I cannot find why....
Maybe someone can help me..
Thank you!!
SwiftHobby

I am working on Fitness App which has pretty similar working CollectionView behavior for it's training plan section. Basically you select certain cell when you finish an exercise. So I played around a lot with way of implementing selecting and deselecting cells. As I see you are missing selecting and deselecting functions of CollectionViewDelegate. There you set different states Effects
Here is the functions you have to use:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if let cell = collectionView.cellForItem(at: indexPath) as? CustomCollectionViewCell {
cell.showIcon()
}
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
if let cell = collectionView.cellForItem(at: indexPath) as? CustomCollectionViewCell {
cell.hideIcon()
}
}

Related

swift - Correct way to pass data to UICollectionViewCell

I'm trying to create my own Book app, and using UICollectionView for listing all the books. Data for each cell is from .plist file, and I'm using custom flowLayout(to make some changes later).
So now I'm stucked with delays and lags when scrolling. I suppose I've made mistakes with passing data to cell or with cell initializing.
Cell created by .xib and custom class, just some layout and UI:
class BookCoverCell: UICollectionViewCell {
#IBOutlet weak var view1: UIView!
#IBOutlet weak var view2: UIView!
#IBOutlet weak var imageView: UIImageView!
#IBOutlet weak var darkRedView: UIView!
#IBOutlet weak var lightRedView: UIView!
#IBOutlet weak var readButton: UIButton!
#IBOutlet weak var pageLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
clipsToBounds = true
self.backgroundColor = UIColor(white: 1, alpha: 0.0)
view1.layer.cornerRadius = 10
view2.layer.cornerRadius = 10
darkRedView.layer.cornerRadius = 10
lightRedView.layer.cornerRadius = 10
}
}
At ViewController's class:
class MainVC: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UIScrollViewDelegate {
#IBOutlet weak var collectionContainerView: UIView!
#IBOutlet weak var collectionView: UICollectionView!
var books: Array<Book>? {
didSet {
collectionView.reloadData()
}
}
var flowLayout = UICollectionViewFlowLayout()
override func viewDidLayoutSubviews() {
flowLayout = ZoomAndSnapFlowLayout()
collectionView.collectionViewLayout = flowLayout
}
override func viewDidLoad() {
super.viewDidLoad()
collectionContainerView.backgroundColor = UIColor(white: 1, alpha: 0.0)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(UINib(nibName: "BookCoverCell", bundle: nil), forCellWithReuseIdentifier: "BookCoverCell");
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
books = BookStore.sharedInstance.loadBooks(plist: "Books")
}
//MARK: UICollectionViewDelegate
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if let books = books {
return books.count
}
return 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "BookCoverCell", for: indexPath) as! BookCoverCell
let book = books![indexPath.row]
let cover = book.coverImage()!
let color = book.getDominantColor()
cell.view1.backgroundColor = color
cell.imageView.image = cover
cell.pageLabel.text = "Pages: 29"
cell.readButton.setTitle("Read", for: .normal)
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let book = books?[indexPath.row]
print ("open")
}
}
And my layout now looks like:
class ZoomAndSnapFlowLayout: UICollectionViewFlowLayout {
var cellWidth = CGFloat()
var cellHeight = CGFloat()
var minLineSpacing = CGFloat()
override init() {
super.init()
self.scrollDirection = .horizontal
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func prepare() {
guard let collectionView = collectionView else { fatalError() }
cellHeight = collectionView.frame.height * 0.8
minLineSpacing = 200
cellWidth = cellHeight
itemSize = CGSize(width: cellWidth, height: cellHeight)
let verticalInsets = (collectionView.frame.height - collectionView.adjustedContentInset.top - collectionView.adjustedContentInset.bottom - itemSize.height) / 2
let horizontalInsets = (collectionView.frame.width - collectionView.adjustedContentInset.right - collectionView.adjustedContentInset.left - itemSize.width) / 2
sectionInset = UIEdgeInsets(top: verticalInsets, left: horizontalInsets, bottom: verticalInsets, right: horizontalInsets)
super.prepare()
}
}
So I'm pretty sure that have some mistakes in my code, but can't find them(( Any suggestion will be helpful for me!
UPDATE:
So first of all thanks!
I've changed my code and replace a really big image with smallest one, and function to get dominant color simply to white color for now, and things already gets better!
BUT there is small lag or delay when first scroll begins, and only with 1st and 2nd cells, while they are scrolling to the left edge of the screen. And after that all cells scrolling without lags, even first two, in both directions (left / right).
Now my code looks like:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "BookCoverCell", for: indexPath) as! BookCoverCell
let book = books![indexPath.row]
let cover = UIImage(named: "flag.png")
let color = #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
cell.view1.backgroundColor = color
cell.imageView.image = cover
cell.pageLabel.text = "Pages: 29"
cell.readButton.setTitle("Read", for: .normal)
return cell
}
and UPDATE #2
removing
flowLayout = ZoomAndSnapFlowLayout()
collectionView.collectionViewLayout = flowLayout
to viewWillAppear() fix these small lags too!! Hooray!)
Read this article about Time Profiling and collection view: https://voxels.github.io/eliminating-collection-view-tearing-with-xcode-time-profiler-instrument
Since your implementation is very simple, there aren't many things that could be wrong, but you probably have the same issue that the author of the article had -- the images themselves are very large, and need to be read and resized.
They solved the issue by making appropriately sized versions of the images.
In your case getDominantColor will also be slower on large images (I am assuming that it reads the pixels to get the dominant color. You should also consider caching this color and not recalculating it every time (if you are not already doing that).

UIButton affecting UICollectionViewCell Appearance

Ok, I have a ViewController that contains a UICollectionView and a UIButton. The Button is Not inside the collection view. When a user taps on an item in the UICollectionView I have some code that changes the background color of a UIView in the cell to show that it is selected. I have similar code for the UIButton which changes its background color when a user taps it, so it is acting like a styled checkbox.
The issue I am seeing is when I tap an item in the collection view, it highlights as it should. But then if I tap on the button, the button highlights normally but the collection view changes which item is highlighted. Even though the button that was tapped is not inside the collection view and has no ties to the collection view.
See following images:
first image: I have tapped on an item in the collection view, see the item is highlighted as normal.
second image: Notice, here, when I tap the Im not sure button, the collectionview changes and the highlight goes to a different cell. But no code in the DidSelectCellForItemAt is running
import Foundation
class SchedulingVisitForViewController: SchedulingViewController, UICollectionViewDelegate, UICollectionViewDataSource {
#IBOutlet weak var collectionView: UICollectionView!
#IBOutlet weak var continueButton: RoundedButton!
#IBOutlet weak var notSureButton: RoundedButton!
#IBOutlet weak var notSureButtonTop: NSLayoutConstraint!
var collectionData = [[String: String]]()
override func viewDidLoad() {
super.viewDidLoad()
self.appointment = fetchAppointment()
self.collectionView.delegate = self
self.collectionView.dataSource = self
self.collectionData.append([
"image": "regularExam",
"text": "Regular Exam",
"data": "regular_exam"])
self.collectionData.append([
"image": "vaccines",
"text": "Essential Vaccines",
"data": "vaccines"])
self.collectionData.append([
"image": "meds",
"text": "Parasite Meds",
"data": "preventitive_meds"])
self.collectionData.append([
"image": "dog",
"text": "My pet is sick",
"data": "sick_pet"])
self.notSureButton.layer.shadowColor = Constants.appColor.gray.light.cgColor
self.notSureButton.layer.shadowOffset = CGSize(width: 0, height: 2.0)
self.notSureButton.layer.shadowRadius = 3.0
self.notSureButton.layer.shadowOpacity = 0.8
self.notSureButton.layer.masksToBounds = false
}
override func viewDidLayoutSubviews(){
self.collectionView.frame = CGRect(x: self.collectionView.frame.origin.x, y: self.collectionView.frame.origin.y, width: self.collectionView.frame.size.width, height: CGFloat(2 * 170))
self.notSureButtonTop.constant = CGFloat(2 * 170) + 20.0
self.view.layoutIfNeeded()
self.collectionView.reloadData()
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.collectionData.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "SelectVisitForCollectionViewCell", for: indexPath) as! SelectVisitForCollectionViewCell
let item = indexPath.item
cell.displayContent(image: UIImage(named: self.collectionData[item]["image"]!)!, text: self.collectionData[item]["text"]!)
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if let cell = collectionView.cellForItem(at: indexPath) as? SelectVisitForCollectionViewCell {
var categories = self.appointment?.categories
if let index = categories!.index(of: self.collectionData[indexPath.item]["data"]!) {
categories?.remove(at: index)
cell.showSelected(false)
} else {
categories?.append(self.collectionData[indexPath.item]["data"]!)
cell.showSelected(true)
}
self.appointment?.categories = categories!
if (self.appointment?.categories.count)! > 0 {
self.continueButton.isHidden = false
} else {
self.continueButton.isHidden = true
}
saveAppointment(data: self.appointment!)
}
}
#IBAction func onNotSureButtonPressed(_ sender: Any) {
var categories = self.appointment?.categories
if let index = categories!.index(of: "not_sure") {
categories?.remove(at: index)
self.notSureButton.backgroundColor = UIColor.white
self.notSureButton.setTitleColor(Constants.appColor.gray.dark, for: .normal)
} else {
categories?.append("not_sure")
self.notSureButton.backgroundColor = Constants.appColor.yellow.main
self.notSureButton.setTitleColor(UIColor.white, for: .normal)
}
self.appointment?.categories = categories!
if (self.appointment?.categories.count)! > 0 {
self.continueButton.isHidden = false
} else {
self.continueButton.isHidden = true
}
print(self.appointment?.categories)
saveAppointment(data: self.appointment!)
}
#IBAction func onContinueButtonPressed(_ sender: Any) {
self.parentPageboy?.scrollToPage(.next, animated: true)
}
}
The issue pointed out by caninster_exister is that I was reloading the collection view

How to NOT reuse cells in UICollectionView - Swift

I have UICollectionView in a cell of UITableView. This UICollectionView displays user's profile photos. There are only a few items there (let's assume 10 photos is a maximum).
So, I do not want to reload images from backend every time cell reusing. It's inconvenient and unfriendly UX. Therefore, I want to create 10 cells, load images into them and show.
But I don't know how to create (instead of dequeuing) new custom cells within "cellForItem" method of UICollectionView. Or how to forbid reusing.
I'm a kind of newbie in programming, so I would be glad if you explained me that in simple words and most detailed. Thank you.
Here is my code:
Setting CollectinView (uploading random local photos in testing purpose):
extension PhotosTableViewCell: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if indexPath.row == 0 {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "AddPhotoCollectionViewCell", for: indexPath)
return cell
} else {
var cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PhotoCollectionViewCell", for: indexPath) as! PhotoCollectionViewCell
cell.photo = UIImage(named: "Photo\(Int.random(in: 1...8))") ?? UIImage(named: "defaultPhoto")
cell.layer.cornerRadius = 10
return cell
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let height = collectionView.frame.height
let width = (height / 16.0) * 10.0
return CGSize(width: width, height: height)
}
}
My custom PhotoCollectionViewCell:
class PhotoCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var imageView: UIImageView!
#IBOutlet weak var heightConstraint: NSLayoutConstraint!
#IBOutlet weak var widthConstraint: NSLayoutConstraint!
var photo: UIImage! {
didSet {
// simulate networking delay
perform(#selector(setImageView), with: nil, afterDelay: Double.random(in: 0.2...1))
// setImageView()
}
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
#objc private func setImageView() {
let cellHeight = self.frame.height
let cellWidth = self.frame.width
let cellRatio = cellHeight / cellWidth
let photoRatio = photo.size.height / photo.size.width
var estimatedWidth = cellWidth
var estimatedHeight = cellHeight
if photoRatio > cellRatio {
estimatedWidth = cellWidth
estimatedHeight = cellWidth * photoRatio
} else {
estimatedHeight = cellHeight
estimatedWidth = cellHeight / photoRatio
}
widthConstraint.constant = estimatedWidth
heightConstraint.constant = estimatedHeight
imageView.image = photo
}
override func prepareForReuse() {
imageView.image = nil
}
}
Just create, fill and return new cell object in cellForItemAt instead of dequeueing it, but it's not a qood practice and you shouldn't do that.
You need to create new cell each time and assign the value to it. Although this is very dangerous thing to do and can cause your app to take so much memory over use your app will use more power and make the device slower.
Instead I suggest that you save your data into a Collection and to keep reusing it as you go.

Found nil while setting value of a label inside Collection view cell

Case:
I'm using a collection view inside a UIView, I've connected it as an outlet called 'partsCollectionView'. I've created a cell with identifier 'cell' and a custom class 'SelectionCollectionViewCell' for that cell. Inside the cell, I've got a label called 'cellTitle'.
Error: Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
I'm facing the error while setting the value of that label inside 'cellForItemAt' Method.
Here are the concerned views:
Cell description,
and, collection View Description
The Cell Class is:
import UIKit
class SelectionCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var cellTitle: UILabel!
}
The class where I'll use the collection view is:
import UIKit
private let reuseIdentifier = "cell"
let array = ["small","Big","Medium","Very big","Toooo big String","small"]
class SelectionCollectionViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
#IBOutlet weak var partsCollectionView: UICollectionView!
#IBOutlet weak var instructionsView: UITextView!
#IBOutlet weak var image: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
self.partsCollectionView.register(SelectionCollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return array.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = partsCollectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! SelectionCollectionViewCell
cell.layer.cornerRadius = 10
cell.clipsToBounds = true
cell.cellTitle.text = array[indexPath.row]
cell.layer.borderColor = #colorLiteral(red: 0.07843137255, green: 0.6862745098, blue: 0.9529411765, alpha: 0.6819349315)
cell.layer.borderWidth = 2
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let name = array[indexPath.row]
let attributedName = NSAttributedString(string: name)
let width = attributedName.widthWithConstrainedHeight(height: 20.0)
return CGSize(width: width + 40, height: 30.0)
}
}
extension NSAttributedString {
func widthWithConstrainedHeight(height: CGFloat) -> CGFloat {
let constraintRect = CGSize(width: .greatestFiniteMagnitude, height: height)
let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, context: nil)
return boundingBox.width
}
}
Update: This is how it looks if I skip setting the title text. The title will come inside those rounded boxes.
Remove this line from viewDidLoad.
self.partsCollectionView.register(SelectionCollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
When you register a cell programmatically it creates a new collection view cell object. It doesn't get the cell from storyboard. So cellTitle will be nil
OR
Programmatically initialize the label in custom collection view cell
class SelectionCollectionViewCell:UICollectionViewCell {
let cellTitle = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(cellTitle)
cellTitle.frame = CGRect(x: 10, y: 10, width: 100, height: 30)
}
}
Just minor mistake is there I think, In cellForItemAt indexPath Please update following line,
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! SelectionCollectionViewCell
I just paste your code and got following output, nothing else,
If you still unable to produce desired output, just delete existing UILabel from SelectionCollectionViewCell and add it again.
FYI. No need to register cell in viewDidLoad() method

Reuse item inside a row when scrolling

I have collectionView inside tableView. collectionView need to horizontal scroll image, tableView for vertical scroll posts. When I had 3 rows I had no problems, but when i create 4 rows i have a problem with scrolling items inside rows. If I start scrolling on 4 row, scrolling is repeated on row 1 and the same thing if i start scrolling on 1 row scrolling is repeating on row 4.
What could be the problem and how to solve it? May be
Can check .gif file. I start on 1 row on name "Oko" and if i scrolling down on 4 row and scroll right collectionCell and return on 1 row i see next image name "City", but there must be name "Oko"
My code:
ViewController:
class PhotoStudiosViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchResultsUpdating {
#IBOutlet weak var tableView: UITableView!
var theStudios: [Studio] = []
var filteredStudios: [Studio] = []
var studiosRef: DatabaseReference!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
tableView.estimatedRowHeight = 475
tableView.rowHeight = UITableViewAutomaticDimension
}
override func viewDidLoad() {
super.viewDidLoad()
studiosRef = Database.database().reference(withPath: "PhotoStudios1")
studiosRef.observe(.value, with: { (snapshot) in
for imageSnap in snapshot.children {
let studioObj = Studio(snapshot: imageSnap as! DataSnapshot)
self.theStudios.append(studioObj)
}
self.tableView.reloadData()
})
}
// MARK: - TableView
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searchController.isActive && searchController.searchBar.text != "" {
return filteredStudios.count
}
return theStudios.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "tableCell", for: indexPath) as! PhotoStudiosTableViewCell
cell.currentPageNumber.text = "1/\(theStudios[indexPath.row].halls.count)"
if searchController.isActive && searchController.searchBar.text != nil {
cell.theHalls = filteredStudios[indexPath.row].halls
} else {
cell.theHalls = theStudios[indexPath.row].halls
}
cell.nameLabel.text = theStudios[indexPath.row].studioName
cell.addressLabel.text = theStudios[indexPath.row].studioAddress
cell.logoLabel.sd_setImage(with: URL(string: theStudios[indexPath.row].studioLogo))
cell.didSelectAction = {
(innerPath) in
self.showDetailsView(indexPath, cellPath: innerPath)
}
return cell
}
TableViewCell:
class PhotoStudiosTableViewCell: UITableViewCell, UICollectionViewDelegate, UICollectionViewDataSource, UIScrollViewDelegate, UICollectionViewDelegateFlowLayout {
#IBOutlet weak var button: UIButton!
#IBOutlet weak var logoLabel: UIImageView!
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var addressLabel: UILabel!
#IBOutlet weak var collectionView: UICollectionView!
#IBOutlet weak var currentPageNumber: UILabel!
var didSelectAction: ((IndexPath) -> ())?
var theHalls: [Hall] = [] {
didSet {
collectionView.reloadData()
}
}
var lastContentOffset = CGPoint.zero
override func prepareForReuse() {
super.prepareForReuse()
resetCollectionView()
}
override func awakeFromNib() {
super.awakeFromNib()
currentPageNumber.layer.zPosition = 2
currentPageNumber.layer.cornerRadius = 15.0
currentPageNumber.clipsToBounds = true
}
func resetCollectionView() {
guard !theHalls.isEmpty else { return }
theHalls = []
collectionView.reloadData()
}
// MARK: - CollectionView
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return theHalls.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionCell", for: indexPath) as! PhotoStudiosCollectionViewCell2
cell.hallName.text = theHalls[indexPath.item].hallName
cell.priceLabel.text = theHalls[indexPath.item].hallPrice
cell.metrslabel.text = theHalls[indexPath.item].hallMetrs
cell.photoStudioImage.sd_setImage(with: URL(string: theHalls[indexPath.item].hallImage))
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
didSelectAction?(indexPath)
}
}
You're reusing the collection views in your cells, which is correct, but that means the contentOffset is also left from whatever was scrolled to previously when a cell is reused. It should be sufficient to just reset the contentOffset in cellForRowAtIndexPath when you are setting up your cell by doing something like:
cell.collectionView.contentOffset = .zero
One thing worth mentioning is that I do see you have a property called lastContentOffset in your cells that doesn't do anything yet and I suspect you are going to try to use that to persist the offset for a given cell when it scrolls out of view so that you can set it again when it comes back into view (rather than always resetting).
If you are going to do that, having the property in the cell won't work. You'll need to have a list of offsets for each cell stored alongside your data models in the containing view controller. Then you might save the offset for a given cell in didEndDisplayingCell and setting it in cellForRowAtIndexPath instead of .zero as I did above.

Resources