Singleton class not updating data on UICollectionViewCell’s with Swift - ios

I am not sure if this is correct way to say this but I have been trying to get data onto ColectionViewCell but it doesn't seem to show all of them. This question follows up from Cards are not displaying different data. Koloda/Yalantis. It appears that the framework requires a different method of applying data to the index but I'm not sure how to achieve this.
Here is how my class looks:
import SwiftyJSON
class Person {
var firstName: String?
init(json: JSON) {
self.firstName = json["first_name"].stringValue
}
}
and here is my singleton that manages the Person class:
class PersonManager {
static let shared = PersonManager()
private init() {}
var persons = [Person]()
func removeAll() {
persons.removeAll()
}
func addPerson(_ person: Person) {
persons.append(person)
}
}
And here is how I try to call the data after it has been fetched and initialised and appended:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
let person = PersonManager.shared.persons[kolodaView.currentCardIndex]
//Returns the first persons first name as it is supposed to
cell.textLabel?.text = person.firstName
}
The data exists as it counts the number of person in my database. But it keeps on returning the first person for all the cards.
Update:
Using Firebase I am fetching the user data and then appending it like this:
func fetchUser() {
let databaseRef = Database.database().reference(withPath: "users")
databaseRef.observe( .value, with: { (snapshot) in
for child in snapshot.children {
guard let snapshot = child as? DataSnapshot else { continue }
let person = Person(from: snapshot)
self.person = person
PersonManager.shared.addPerson(person)
}
self.kolodaView.reloadData()
})
}

Try changing your function as follows:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
let person = PersonManager.shared.persons[indexPath.row]
//Returns the first persons first name as it is supposed to
cell.textLabel?.text = person.firstName
}
The problem is that your variable kolodaView.currentCardIndex is always 0, it will never show other results but the first in the set.
indexPath.row is meant to be used to control this index you need.

May be you should try indexPath.item, not row(That's for UITableView)
And make sure
let cell = collectionView.dequeueReusableCell(withReuseIdentifier:
"cell", for: indexPath)
cell is not nil. You should print every cells.If it's nil, you can't put your data in a nil UICollectionViewCell.
So add code
if(cell == nil) cell = [UICollectionViewCell new];

I suppose in:
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return PersonManager.shared.persons.count
}
And in cellForItem you should use indexPath.row instead of kolodaView.currentCardIndex unless this is really what you need(very weird).
If so, check if PersonManager append same Person every time, which gives you only your first item every time. Double check it, because there is no another reason

Related

Return Cell Based On Model Data In Switch Statement

I have a model that holds a bool value. I would like to return a UICollectionViewCell based on the value of the bool within the model using a switch statement. However, I am getting a casting error.
Could not cast value of type 'Project.FalseCell' (0x10077a9f0) to 'Project.TrueCell' (0x100777ef8).
I've checked the following;
I have registered my cells in viewDidLoad
I have unique identifiers for each cell type
What am I missing here?
// MODEL
struct Model {
let bool: Bool
}
// CONTROLLER
let models = [Model]()
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let model = models[indexPath.item]
switch model.bool {
case true:
let trueCell: TrueCell = listView.collectionView.dequeueReusableCell(withReuseIdentifier: TrueCell.identifier, for: indexPath) as! TrueCell
return trueCell
case false:
let falseCell: FalseCell = listView.collectionView.dequeueReusableCell(withReuseIdentifier: FalseCell.identifier, for: indexPath) as! FalseCell
return falseCell
}
}
As per #goat_herd 's comment, there was an issue with using cell identifiers as String(describing: Self) as I had renamed the Class at one point and this caused a disruption in the identifier.
I changed the identifier to a raw valued string and this solved the issue.

Update a single view with uicollection view Swift

I have a view with a grid movies. There's three properties: title Label, image view for poster movie and a image to show if a movie is or not favored.
When click on a cell, go on to a detail view with show others infos about the movie and there's a button action to favor a movie. I wish update the button icon in grid collection view. So I create a delegate to listen when this event occurs and then reload the collection view.
Screenshots:
grid movies screen
detail movie screen
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath) as! MoviesCollectionViewCell
cell.titleLabel.text = popularMovies[indexPath.row].title
getImageMovies(imageURLString: popularMovies[indexPath.row].poster, imageView: cell.movieImage)
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let detailMovieViewController = DetailMovieViewController()
detailMovieViewController.titleMovieLabel.text = popularMovies[indexPath.row].title
detailMovieViewController.releaseDateMovieLabel.text = popularMovies[indexPath.row].date
detailMovieViewController.overviewMovieLabel.text = popularMovies[indexPath.row].overview
getImageMovies(imageURLString: popularMovies[indexPath.row].poster, imageView: detailMovieViewController.movieImage)
getGenresMovies(genresMoviesID: popularMovies[indexPath.row].genre, genreMovieLabel: detailMovieViewController.genreMovieLabel)
collectionView.deselectItem(at: indexPath, animated: true)
detailMovieViewController.delegate = self
selectedIndexPath = indexPath
self.navigationController?.pushViewController(detailMovieViewController, animated: true)
}
protocol favoriteMovieDelegate: class {
func updateFavoriteImage ()
}
#objc func markFavoriteButtom (buttom: UIButton){
if buttom.isSelected == false {
buttom.isSelected = true
}else {
buttom.isSelected = false
}
delegate?.updateFavoriteImage()
}
func updateFavoriteImage() {
if let indexPath = selectedIndexPath {
let cell = collectionView.cellForItem(at: indexPath) as! MoviesCollectionViewCell
cell.favoriteIconImage.image = UIImage(named: "favorite_full_icon")
collectionView.reloadData()
}
}
struct Films: Codable {
let id: Int
let title: String
let poster: String
let genre: [Int]
let date: String
let overview: String
enum CodingKeys: String, CodingKey {
case id
case title
case poster = "poster_path"
case genre = "genre_ids"
case date = "release_date"
case overview
}
}
Instead of updating directly your cell.favoriteIconImage.image you will have to update your object directly and then reload CollectionView. you need to set fav image on cellForItem Also you need to post that to server once user make it fav ... and fetch isFav from server if you want to persist data. And if your server did not support isFav then you need to store it locally against film Id ... in user default

Not able to append the value and display in collection view

I have one api call, in that where i will fetch all the names and i am appending to one var to display in my collection view label.but values are not appending to my var.
here code :
var mobkam = [String]()
override func viewDidLoad() {
super.viewDidLoad()
self.getallLoans()
}
func getallLoans(){
Manager.sharedInstance.getallLoans { (data, err) in
if let _ = err{
}else{
if let dataa = data as? String{
if let dataFromString = dataa.data(using: String.Encoding.utf8, allowLossyConversion: false) {
let json = JSON(data: dataFromString)
print(json) // correctly display all names like ["1","2", etc]
self.mobileOprator.removeAll()
for (_, val) in json {
print(val.rawString()) // displaying the correct each items names
self.mobkam.append(val.rawString()!)
}
}
}
}
}
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.mobkam.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath as IndexPath) as! NameCollectionViewCell
cell.NameLabel.text = self.mobkam[indexPath.item]
return cell
}
What i m doing wrong?. Not able to solve.Am i missed any.Please help me out.
Thanks !
You just need to call collectionView.reloadData() after you have loaded all of the values in getallLoans.
When you make the API call, this is an asynchronous task that will take a little time to complete. Your collection view will have already loaded it's data source so you need to inform it that the data has changed. It will then call the CollectionViewDataSource delegate methods again and refresh the view based on the updated data.
for (_, val) in json {
print(val.rawString()) // displaying the correct each items names
self.mobkam.append(val.rawString()!)
}
collectionView.reloadData()

Is this the right way to populate UICollectionView (inside a table view cell) with Firebase?

Problem: TableView is very slow when scrolling. Looks like my code is not efficient at all.
So I have a UICollectionView embedded inside a tableViewCell like so (I used this tutorial to accomplish it.)
I am using Firebase to populate data into the UICollectionViewCells. I have 3 class folders:
TableViewCtrl: Responsible for downloading section titles and then passing some logic to tableViewCell. Here is partial code of the main TableViewCtrl:
// 1. DOWNLOAD SECTION TITLES AND THEN CALL RELOADTABLE
// 2. TableView:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return featuredCollection.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCell(withIdentifier: "browseCell", for: indexPath) as! BrowseCell
cell.configureCell(of: featuredCollection[indexPath.row].typeOfItem, title: featuredCollection[indexPath.row].categoryTitle, bookReference: featuredCollection[indexPath.row].bookReference, spotlightTests: featuredCollection[indexPath.row].spotlightTests, bookUniqueIDsToDownload: featuredCollection[indexPath.row].bookUniqueIDsToDownload)
return cell
}
TableViewCell:
func configureCell(of type: FeaturedItem, title: String, bookReference: BookReferenceTest?, spotlightTests: [BookReferenceTest]?, bookUniqueIDsToDownload: [String]?) {
setCollectionViewDataSourceDelegate(delegate: self, dataSource: self)
// DOWNLOAD THE BOOK ITEMS (eg. IMAGES, TITLES, ETC) then call self.collectionView.reloadData()
}
internal func setCollectionViewDataSourceDelegate <D: UICollectionViewDelegate, S: UICollectionViewDataSource>(delegate: D, dataSource: S) {
collectionView.delegate = delegate
collectionView.dataSource = dataSource
let collectionViewFlowLayout = UICollectionViewFlowLayout()
let myCollectionView = UICollectionView(frame: self.collectionView.bounds, collectionViewLayout: collectionViewFlowLayout)
myCollectionView.delegate = self
myCollectionView.dataSource = self
collectionView.reloadData()
}
// Snipit of code that's responsible for downloading book assets:
func downloadBrowsingBooks(bookUniqueKeys: [String]) {
let databaseReference = FIRDatabase.database().reference()
databaseReference.child("Kutub/Books/").observeSingleEvent(of: .value, with: {
(snapshot) in
var books = [BrowsingBook]()
for book in (snapshot.children.allObjects as! [FIRDataSnapshot]) {
if bookUniqueKeys.contains((book.key)) {
let browsingBookValues = book.value as! [String : AnyObject]
let browsingBook = self.createBrowsingBookObject(data: browsingBookValues, uniqueKey: book.key)
books.append(browsingBook)
}
}
self.storedBooks = books
self.collectionView.reloadData()
})
}
internal func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "booksCollectionCell", for: indexPath) as! BooksCollectionCell
let bookTitle = storedBooks[indexPath.item].title
let authors = storedBooks[indexPath.item].authors
cell.configureCell(title: bookTitle, authorNames: authors)
return cell
}
UICollectionViewCell:
func configureCell(title: String, authorNames: [String]? = nil, imageCover: UIImage? = nil) {
var authorName = ""
if let authors = authorNames {
authorName = authors[0]
for index in 1..<authors.count {
authorName += ", \(authors[index])"
}
}
// ....
}
From my understanding, here's step-by-step of what's happening:
Section titles are downloaded
TableView.reload() configures the tableViewCells
Inside tableViewCells, firebase downloads images and other book assets (eg. titles, authors, publishers names in text from Firebase database) and calls on collectionView
CollectionView configures it's cells.
Again, my main problems is that scrolling is very slow and laggy with the way that I'm doing this. When I tried different methods (eg. downloading the data and passing it on to tableviewCell) it works but when I add items to Firebase database only section titles show up and not the content inside the collectionViewCells.

Current Index associating with wrong data

I have a collection view that scrolls horizontally and each cell pushes to a detail view upon a tap. When I load the app, I have it print the object at index. At first it will load the one cell it is supposed to. But when I scroll over one space it prints off two new ids, and then begins to associate the data of the last loaded cell with the one currently on the screen, which is off by one spot now. I have no clue how to resolve this, my best guess is there is some way to better keep up with the current index or there is something in the viewDidAppear maybe I am missing. Here is some code:
open var currentIndex: Int {
guard (self.collectionView) != nil else { return 0 }
return Int()
}
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ExpandCell", for: indexPath) as! ExpandingCollectionViewCell
let object = self.imageFilesArray[(indexPath).row] as! PFObject
cell.nameLabel.text = object["Name"] as! String
whatObject = String(describing: object.objectId)
print(whatObject)
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let object = self.imageFilesArray[(indexPath as NSIndexPath).row]
guard let cell = collectionView.cellForItem(at: indexPath) as? ExpandingCollectionViewCell else { return }
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
let nextViewController = storyBoard.instantiateViewController(withIdentifier: "EventDetailViewController") as! EventDetailViewController
nextViewController.lblName = nameData
nextViewController.whatObj = self.whatObject
self.present(nextViewController, animated:false, completion:nil)
}
Does anyone know a better way to set the current index so I am always pushing the correct data to the next page?
The data source that is setting the elements of the cell can keep count of index that can also be set as a property and can be used to retrieve back from the cell to get correct current index.
EventDetailViewController is your UICollectionViewController subclass I assume.
If this is correct then you need to implement on it:
A method that tells the collection how many items there are in the datasource.
//1
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1 // you only have 1 section
}
//2
override func collectionView(_ collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int {
return array.count// this is the number of models you have to display... they will all live in one section
}
A method that tells the collection which cellView subclass to use for that given row.
-A method that populates the cell with it's datasource.
Follow this tutorial from which I extracted part of the code, they convey this core concepts pretty clearly.
Reusing, as per Apple's docs happens for a number of cells decided by UIKit, when you scroll up a little bit 2 or N cells can be dequed for reusing.
In summary, with this three methods you can offset your collection into skipping the one record that you want to avoid.
Happy coding!

Resources