Not able to append the value and display in collection view - ios

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()

Related

How to Load More Posts In CollectionView?

i am making app with Xcode using Swift , i fetch posts from my WordPress Website, i am very new to Xcode and Swift , i have fetched posts from my Website Successfully , now the problem is that when is try to load more posts (More than 10 posts) , i mean pagination, i see some problems, like when i do pagination after 10th post, it show the next 11-20 posts but it not starts from post 11th but it goes directly to post 20 and because of that , all next posts loaded automatically untill the end of posts, and one more thing when next posts are loading than i can't see the old posts like when 11-20 posts loaded then 1-10 posts are not shown and CollectionView starts from number 11.
this is my code to fetch posts..
func fetchPostData(completionHandler: #escaping ([Post]) -> Void ) {
self.page += 1
let url = URL(string: "https://www.sikhnama.com/wp-json/wp/v2/posts/?categories=6&page=\(page)")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else {return}
do {
let postsData = try JSONDecoder().decode([Post].self, from: data)
completionHandler(postsData)
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
catch {
let error = error
print(String(describing: error))
}
}.resume()
}
in ViewDid Load
self.fetchPostData { (posts) in
self.newsData = posts }
and this is how i do pagination
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
if indexPath.row == self.newsData.count - 1 { //numberofitem count
updateNextSet()
}
}
func updateNextSet(){
self.fetchPostData { (posts) in
self.newsData = posts
}
}
CollectionView Code
extension ViewController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int{
return self.newsData.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MovieCollectionViewCell", for: indexPath) as! MovieCollectionViewCell
cell.setup(with: newsData[indexPath.row])
return cell
}
please help . thanks
When the next batch of posts come back, you are overwriting the existing posts rather than appending them:
self.fetchPostData { (posts) in
self.newsData = posts
}
Instead, you need to append the new posts instead such as by using the += operator.
In addition, you should call reloadData() within the fetchPostData completion handler rather than inside the response of the HTTP request to give your code better separation of concerns.
So the above code would become:
self.fetchPostData { (posts) in
self.newsData += posts
self.collectionView.reloadData()
}

Singleton class not updating data on UICollectionViewCell’s with Swift

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

How to show local images in collection view with api called data labels names respectively

Means !
I have one collection view which will have the image, label.
And i am doing one api call , to display the list of available name in my collection view label. For image i am giving the static images.
Now in my local image assets like start from names like img1, img2,img3,img4 like that.
What i need is i need to show all the images one by one as per my label names.
Like this is my example :
Label : Apple,Orange,drums [ I will have this order only no change]
Image :
Apple image Named as img1
orange image Named as img2
drums image Named as img3
Now how can i so the respective images as per the label name.Here my code :
var AllNames = [String]()
override func viewWillAppear(_ animated: Bool) {
self.getNames()
}
func getNames(){
Transport.sharedInstance.getname { (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)
self.mobileOprator.removeAll()
for (_, val) in json {
self.AllNames.append(val.rawString()!)
}
}
}
self.collectionView.reloadData()
}
}
}
So my AllNames will have all names. How can i achive my requirement.
Thanks !
Not sure if this is the think you want to have, but anyway: you have a function called cellForItemAtIndexPath for creating the cells and configure them. This could look like that (plus numberOfItemsInSection):
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return allNames.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let myCell = collectionView.dequeueReusableCell(withReuseIdentifier: "MyCell", for: indexPath) as! MyCell
let imageName = "img\(indexPath.row + 1)"
myCell.imageView.image = UIImage(named: imageName)
myCell.label.text = allNames[indexPath.row]
return myCell
}
The special line you are looking for is this here:
let imageName = "img\(indexPath.row + 1)"
Not sure if that's what you are looking for?!

CollectionView in TableView displays false Data swift

I'm trying to combine a CollectionViewwith a TableView, so fare everything works except one problem, which I cant fix myself.
I have to load some data in the CollectionViews which are sorted with the header of the TableViewCell where the CollectionView is inside. For some reason, every time I start the app, the first three TableViewCells are identical. If I scroll a little bit vertically, they change to the right Data.
But it can also happen that while using it sometimes displays the same Data as in on TableViewCell another TableViewCell, here again the problem is solved if I scroll a little.
I think the problem are the reusableCells but I cant find the mistake myself. I tried to insert a colletionView.reloadData() and to set the cells to nil before reusing, sadly this didn`t work.
My TableViewController
import UIKit
import RealmSwift
import Alamofire
import SwiftyJSON
let myGroupLive = DispatchGroup()
let myGroupCommunity = DispatchGroup()
var channelTitle=""
class HomeVTwoTableViewController: UITableViewController {
var headers = ["LIVE","Channel1", "Channel2", "Channel3", "Channel4", "Channel5", "Channel6"]
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
self.navigationController?.navigationBar.isTranslucent = false
DataController().fetchDataLive(mode: "get")
DataController().fetchDataCommunity(mode: "get")
}
//MARK: Custom Tableview Headers
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return headers[section]
}
//MARK: DataSource Methods
override func numberOfSections(in tableView: UITableView) -> Int {
return headers.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
//Choosing the responsible PrototypCell for the Sections
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellBig", for: indexPath) as! HomeVTwoTableViewCell
print("TableViewreloadMain")
cell.collectionView.reloadData()
return cell
}
else if indexPath.section >= 1 {
// getting header Titel for reuse in cell
channelTitle = self.tableView(tableView, titleForHeaderInSection: indexPath.section)!
let cell = tableView.dequeueReusableCell(withIdentifier: "cellSmall", for: indexPath) as! HomeVTwoTableViewCellSmall
// anti Duplicate protection
cell.collectionView.reloadData()
return cell
}
else {
channelTitle = self.tableView(tableView, titleForHeaderInSection: indexPath.section)!
let cell = tableView.dequeueReusableCell(withIdentifier: "cellSmall", for: indexPath) as! HomeVTwoTableViewCellSmall
// anti Duplicate protection
cell.collectionView.reloadData()
return cell
}
}
}
}
My TableViewCell with `CollectionView
import UIKit
import RealmSwift
var communities: Results<Community>?
class HomeVTwoTableViewCellSmall: UITableViewCell{
//serves as a translator from ChannelName to the ChannelId
var channelOverview: [String:String] = ["Channel1": "399", "Channel2": "401", "Channel3": "360", "Channel4": "322", "Channel5": "385", "Channel6": "4"]
//Initiaize the CellChannel Container
var cellChannel: Results<Community>!
//Initialize the translated ChannelId
var channelId: String = ""
#IBOutlet weak var collectionView: UICollectionView!
}
extension HomeVTwoTableViewCellSmall: UICollectionViewDataSource,UICollectionViewDelegate {
//MARK: Datasource Methods
func numberOfSections(in collectionView: UICollectionView) -> Int
{
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
return (cellChannel.count)
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionCellSmall", for: indexPath) as? HomeVTwoCollectionViewCellSmall else
{
fatalError("Cell has wrong type")
}
//removes the old image and Titel
cell.imageView.image = nil
cell.titleLbl.text = nil
//inserting the channel specific data
let url : String = (cellChannel[indexPath.row].pictureId)
let name :String = (cellChannel[indexPath.row].communityName)
cell.titleLbl.text = name
cell.imageView.downloadedFrom(link :"link")
return cell
}
//MARK: Delegate Methods
override func layoutSubviews() {
myGroupCommunity.notify(queue: DispatchQueue.main, execute: {
let realm = try! Realm()
//Getting the ChannelId from Dictionary
self.channelId = self.channelOverview[channelTitle]!
//load data from Realm into variables
self.cellChannel = realm.objects(Community.self).filter("channelId = \(String(describing: self.channelId)) ")
self.collectionView.dataSource = self
self.collectionView.delegate = self
print("collectionView layout Subviews")
self.collectionView.reloadData()
})
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
selectedCommunity = (cellChannel[indexPath.row].communityId)
let home = HomeViewController()
home.showCommunityDetail()
}
}
Thanks in advance.
tl;dr make channelTitle a variable on your cell and not a global variable. Also, clear it, and your other cell variables, on prepareForReuse
I may be mistaken here, but are you setting the channelTitle on the cells once you create them? As I see it, in your viewController you create cells based on your headers, and for each cell you set TableViewController's channelTitle to be the title at the given section.
If this is the case, then the TableViewCell actually isn't receiving any information about what it should be loading before you call reloadData().
In general, I would also recommend implementing prepareForReuse in your HomeVTwoTableViewCellSmall, since it will give you a chance to clean up any stale data. Likely you would want to do something like set cellChannel and channelId to empty strings or nil in that method, so when the cell is reused that old data is sticking around.
ALSO, I just reread the cell code you have, and it looks like you're doing some critical initial cell setup in layoutSubviews. That method is going to be potentially called a lot, but you really only need it to be called once (for the majority of what it does). Try this out:
override the init with reuse identifier on the cell
in that init, add self.collectionView.dataSource = self and self.collectionView.delegate = self
add a didSet on channelTitle
set channelTitle in the viewController
So the code would look like:
var channelTitle: String = "" {
didSet {
self.channelId = self.channelOverview[channelTitle]!
self.cellChannel = realm.objects(Community.self).filter("channelId = \(String(describing: self.channelId)) ")
self.collectionView.reloadData()
}
}
This way you're only reloading your data when the cell is updated with a new channel, rather than every layout of the cell's views.
Sorry... one more addition. I wasn't aware of how your channelTitle was actually being passed. As I see it, you're using channelTitle as a global variable rather than a local one. Don't do that! remove channelTitle from where it is currently before implementing the code above. You'll see some errors, because you're setting it in the ViewController and accessing it in the cell. What you want is to set the channelTitle on the cell from the ViewController (as I outlined above). That also explains why you were seeing the same data across all three cells. Basically you had set only ONE channelTitle and all three cells were looking to that global value to fetch their data.
Hope that helps a little!
(also, you should be able to remove your else if block in the cellForRowAtIndexPath method, since the else block that follows it covers the same code. You can also delete your viewDidLoad, since it isn't doing anything, and you should, as a rule, see if you can get rid of any !'s because they're unsafe. Use ? or guard or if let instead)

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.

Resources