swift different number of cell in each section - swift

I'm trying to pull data from firebase and show it in a collectionView with sections. How can add a different number of cells per section as each firebase data node will not have the same number of posts.
var users = [User]()
var posts = [Post]()
Updated collection view methods:
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! FollowingCell
let user = self.users[indexPath.section]
let post = self.dataSource[user]?[indexPath.row]
cell.post = post
return cell
}
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: headerId, for: indexPath) as! FollowingHeader
let user = self.users[indexPath.section]
header.user = user
return header
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
let user = self.users[section]
return self.dataSource[user]?.count ?? 0
}
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return self.dataSource.count
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 120, height: 80)
}
How I'm adding the posts and user. As you can see I'm adding the post to the dataSource but for some reason, only 1 post per use is added when one of the users has two posts.
func fetchFollowedEvents() {
self.collectionView.refreshControl?.endRefreshing()
guard let currentUid = Auth.auth().currentUser?.uid else { return }
let ref = Database.database().reference().child("followed-posts").child(currentUid)
ref.observeSingleEvent(of: .value) { (snapshot) in
guard let dictionary = snapshot.value as? [String: Any] else { return }
dictionary.forEach { (key, value) in
Database.database().reference().child("posts").child(key).observeSingleEvent(of: .value) { (postSnapshot) in
guard let postDictionary = postSnapshot.value as? [String: Any] else { return }
guard let uid = postDictionary["uid"] as? String else { return }
Database.fetchUserWithUID(uid: uid) { (user) in
let post = Post(postId: key, user: user, dictionary: postDictionary)
post.id = key
self.users.append(user)
self.dataSource[user] = [post]
self.collectionView.reloadData()
}
}
}
}
}

try creating data source like this
var users : [User] = []()
var dataSource : [User:[Post]] = [:]() //I Prefer this one
(or)
var dataSource :[Int: [Post]] = [:]()
a dictionary with Users as Key and Array of post as Value
So now for number of sections you can use
self.userser.count()
(or)
self.dataSource.count()
So for number of rows in sections, you can do as so
let user = self.users[section]
return self.dataSource[user].count() ?? 0
(or)
return self.dataSource[section].count() ?? 0
in cell for row at index you can get the data as
let user = self.users[indexPath.section]
let data = self.dataSource[user][indexPath.row]
(or)
let data = self.dataSource[indexPath.section][indexPath.row]
Hope this would help you.
Happy Codding!

Related

Collectionview with coredata problems

The problem is when I switch between pages (TabNavigation) and I return in this page, a cell is added unwantedly, I rewrite the code the code many times, can someone help me?
CoreData is implemented it to save favorites in this collection view, and everything works except this little bug
var Distance : String!
var Logo : UIImage!
var pp : String!
var menuu : UIButton!
var loc : String!
var shop: [NSManagedObject] = []
#IBOutlet weak var ShopCollectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
ShopCollectionView.layer.cornerRadius = 10
ShopCollectionView.layer.masksToBounds = true
// register cell
let nibCell = UINib(nibName: ShopCollectionViewCellId, bundle: nil)
ShopCollectionView.register(nibCell, forCellWithReuseIdentifier: ShopCollectionViewCellId)
ShopCollectionView.delegate = self
ShopCollectionView.dataSource = self
let center = UNUserNotificationCenter.current()
center.requestAuthorization(options: [.alert, .sound]) { (granted, error) in }
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
let managedContext = appDelegate.persistentContainer.viewContext
let entity = NSEntityDescription.entity(forEntityName: "ShopsData", in: managedContext)!
let item = NSManagedObject(entity: entity, insertInto: managedContext)
let fetch = NSFetchRequest<NSFetchRequestResult>(entityName: "ShopsData")
do {
let result = try? managedContext.fetch(fetch) as? [ShopsData]
shop = result ?? []
} catch {
fatalError()
}
collectionView.reloadData()
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of items
return shop.count + 1
}
<This is my writed method>
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if indexPath.row >= shop.count {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ShopCollectionViewCellId, for: indexPath) as! ShopCollectionViewCell
return cell
} else {
let shop = shop[indexPath.row]
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ShopCollectionViewCellId, for: indexPath) as! ShopCollectionViewCell
cell.People.text = shop.value(forKey: "actualCustomers") as? String
cell.Location.text = shop.value(forKey: "location") as? String
return cell
}
}
This is the code I write
In your numberOfItemsInSection you return shop.count + 1 which means you are telling your collection view to show you 1 additional cell than the actual data you have.
Then in your cellForItemAt indexPath, you handle this by creating 1 blank cell.
If suggest you make the following changes to these functions as shown below and perhaps you will see the results you expect
override func collectionView(_ collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int
{
return shop.count
}
override func collectionView(_ collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
let shop = shop[indexPath.row]
let cell =
collectionView.dequeueReusableCell(withReuseIdentifier: ShopCollectionViewCellId,
for: indexPath) as! ShopCollectionViewCell
cell.People.text = shop.value(forKey: "actualCustomers") as? String
cell.Location.text = shop.value(forKey: "location") as? String
return cell
}

Problem adding Values in Array in coolectionView - Swift

I pull data from the database. I am separating the data I have taken according to the number of CATEGORIDAVTOPLAM.count. For example, there is 4 data in the first section and 3 data in the second section. However, while the data in 2.section should be 5.6.7 items in the new DavFile array, 1.2.7 in Array. attracts the next data. How can I add data by counting in the section in a single directory? When I do as follows, I have 7 data in total as follows. The ranking in the first section is 1.2.3.4 in Array, and the ranking in the second section is 1.2.7. values.
func getIndexForArray(with indexPath:IndexPath)->Int{
var itemIndex = 0
if indexPath.row != 0{
for _ in 0..<indexPath.section{
itemIndex += (sonsuzCollec.numberOfItems(inSection: indexPath.section) - 1)
}
return (itemIndex + indexPath.row)
}else{
return indexPath.row
}
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return Int(KATEGORIDAVTOPLAM[section]) ?? 0 }
func numberOfSections(in collectionView: UICollectionView) -> Int {
return yeniDavKATIsımNew.count }
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellSonsuz", for: indexPath) as! sonsuzCell
let currentIndex = getIndexForArray(with: indexPath)
if currentIndex < yeniDavFile.count{
let urlNew = URL(string: yeniDavFile[currentIndex])
cell.davetiyeFoto.sd_setImage(with: urlNew)
return cell
}else{
return UICollectionViewCell()
}
#objc func kategoriSaydırNew(){
...
if let baslik = try JSONSerialization.jsonObject(with: data, options: []) as? [[String: Any]] {
for review in baslik {
if let soru_baslik = review["davetiyefilee"] as? String {
let s = String(describing: soru_baslik)
self.yeniDavFile.append(s)
} }
DispatchQueue.main.async { self.sonsuzCollec.reloadData() }}
if let baslik = try JSONSerialization.jsonObject(with: data, options: []) as? [[String: Any]] {
for review in baslik {
if let soru_baslik = review["TOPLAM"] as? Int {
let s = String(describing: soru_baslik)
self.KATEGORIDAVTOPLAM.append(s)
} }
DispatchQueue.main.async {
self.sonsuzCollec.reloadData()
}

CollectionViewCell is no displayed receiving data from Firebase

I have to fill the cells with data from the firebase. But they are not displayed. Help and explain where I made a mistake.
How to fill cell data ?
class TrainingProgramViewController: UIViewController {
#IBOutlet weak var collectionView: UICollectionView!
//var trainingPrograms = TrainingProgram.fetchTrainingProgram()
var trainingPrograms = [TrainingProgram]()
let cellScale: CGFloat = 0.7
override func viewDidLoad() {
super.viewDidLoad()
fetchPrograms()
}
func fetchPrograms() {
Database.database().reference().child("programs").observe(.childAdded) { (snapshot) in
if let dict = snapshot.value as? [String: Any] {
print(dict)
let newTitle = dict["title"] as! String
print("Новый тайтл:" + newTitle)
let newDescription = dict["description"] as! String
let trainingCell = TrainingProgram(description: newDescription, title: newTitle)
self.trainingPrograms.append(trainingCell)
print(self.trainingPrograms)
}
}
}
}
extension TrainingProgramViewController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return trainingPrograms.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "TrainingProgramCollectionViewCell", for: indexPath) as! TrainingProgramCollectionViewCell
let trainingProgram = trainingPrograms[indexPath.item]
cell.trainingPrograms = trainingProgram
return cell
}
This is a model:
class TrainingProgram
{
var description = ""
var title = ""
init(description: String, title: String) {
self.description = description
self.title = title
}
}
This is my structure of Database :
You forget to reload your collectionView
func fetchPrograms() {
Database.database().reference().child("programs").observe(.childAdded) { (snapshot) in
if let dict = snapshot.value as? [String: Any] {
print(dict)
let newTitle = dict["title"] as! String
print("Новый тайтл:" + newTitle)
let newDescription = dict["description"] as! String
let trainingCell = TrainingProgram(description: newDescription, title: newTitle)
self.trainingPrograms.append(trainingCell)
print(self.trainingPrograms)
}
DispatchQueue.main.async {
self. collectionView.reloadData()
}
}
}
Also set
collectionView.delegate = self
collectionView.dataSource = self
In your collectionView Method
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "TrainingProgramCollectionViewCell", for: indexPath) as! TrainingProgramCollectionViewCell
cell.titleLabel.text = trainingPrograms[indexPath.item].title
return cell
}
After you fetch the data and append it to your array which is shown in your tableView/collectionView you always have to reloadData() like collectionView.reloadData() or tableView.reloadData()

dynamically load sections of UIController depending on data

I am trying to implement a UICollectionView for a "Add Friends" page in my application and I need the following functionality:
When the view first appears, display and friend requests that a user may have.
User inputs username into search bar, and then (if found) that username shows up in a cell ABOVE the cells for friend requests.
I have been trying to implement this by using sections in a UICollevtionViewController but it has been a headache...
import UIKit
import Firebase
class AddUserController: UICollectionViewController, UICollectionViewDelegateFlowLayout, UISearchBarDelegate {
var searchedUser: User?
var friendRequests = [User]()
let cellId = "cellId"
let headerId = "headerId"
var sectionCategories = ["Add Friends", "Friend Requests"]
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.delegate = self
collectionView?.dataSource = self
collectionView?.register(SearchedUserCell.self, forCellWithReuseIdentifier: cellId)
collectionView?.register(SectionHeaderView.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: headerId)
getFriendRequests()
}
fileprivate func fetchSearchedUsers(username: String) {
let ref = Database.database().reference().child("usernames")
ref.child(username).observeSingleEvent(of: .value, with: { (snapshot) in
// populate user info
guard let uidOfSearchedUser = snapshot.value as? String else {return}
Database.database().reference().child("users").child(uidOfSearchedUser).observeSingleEvent(of: .value, with: { (snapshot) in
guard let userDictionary = snapshot.value as? [String: Any] else {return}
self.searchedUser = User(uid: uidOfSearchedUser, dictionary: userDictionary)
self.collectionView?.reloadData()
}, withCancel: { (err) in
print("error retrieving user", err)
})
}) { (err) in
print("unable to find user: ", err)
}
}
func getFriendRequests() {
guard let uid = Auth.auth().currentUser?.uid else {return}
Database.database().reference().child("users").child(uid).child("requests").observe(.value) { (snapshot) in
guard let requestsDictionary = snapshot.value as? [String: Any] else {return}
for (key, value) in requestsDictionary {
print(key, value)
Database.database().reference().child("users").child(key).observeSingleEvent(of: .value, with: { (snapshot) in
guard let userDictionary = snapshot.value as? [String: Any] else {return}
let user = User(uid: key, dictionary: userDictionary)
print("friend request from: ", user.username)
self.friendRequests.append(user)
})
}
}
self.collectionView?.reloadData()
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if section == 0 {
return 4
} else if section == 1 {
return 1
}
return 0
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let sendFriendRequestcell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! SearchedUserCell
let dealWithFriendRequestCell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! SearchedUserCell
if indexPath.section == 0 {
//Cell for searched user
sendFriendRequestcell.user = searchedUser
return sendFriendRequestcell
} else if indexPath.section == 1 {
//Populate all the friend requests that exist
if friendRequests.count != 0 {
dealWithFriendRequestCell.user = friendRequests[indexPath.item]
}
return dealWithFriendRequestCell
}
return sendFriendRequestcell
}
override func numberOfSections(in collectionView: UICollectionView) -> Int {
//Change this to return only 2 only if friend requests exist.
return 2
}
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
switch kind {
case UICollectionElementKindSectionHeader:
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: headerId, for: indexPath) as! SectionHeaderView
headerView.sectionName = sectionCategories[indexPath.section]
return headerView
default:
assert(false, "Unexpected element kind")
}
}
}
Im using firebase to store/fetch both my searched users and any friend requests. I can see users in the friend requests.
The issues I'm having are:
Dynamically showing sections (the "Add Friends" section and cells shouldn't be displayed unless user searches for a user.
This particular user (screenshot) has a friend request, but the cell isn't populated to show that. I can see that I am retrieving user correctly.
Is a collection view like this even the best way for me to handle this? Seems a bit complicated.
I say your problem is state. Here is an example how state can be modeled with an enum with associated values. Read more on enums here.
struct User {}
struct UserRequest {}
class Controller: NSObject {
var collectionView: NSCollectionView!
enum Sections: Int {
case searched = 0
case requests = 1
}
enum State {
case none
case requests(requests: [UserRequest])
case searched(user: User)
case searchedAndRequests(user: User, requests: [UserRequest])
}
var state: State = .none {
didSet {
collectionView.reloadData()
}
}
}
extension Controller: NSCollectionViewDataSource {
func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
switch state {
case .requests(let requests):
return requests.count
case .searched(_):
return 1
case .searchedAndRequests(_, let requests):
switch Section(rawValue: section)! {
case .searched:
return 1
case .requests:
return requests.count
default:
fatalError("section not allowed.")
}
default:
fatalError("invalid state.")
}
}
func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
// Create cell(s) here or later.
switch state {
case .searched(let user):
// Populate user cell and return it.
break
case .requests(let requests):
let request = requests[indexPath.item]
// Populate cell with request and return it.
case .searchedAndRequests(let user, let requests):
switch Section(rawValue: indexPath.section)! {
case .searched:
// Populate user cell and return it.
break
case .requests:
let request = requests[indexPath.item]
// Populate cell with request and return it.
default:
fatalError("section not allowed.")
}
default:
fatalError("invalid state")
}
}
func numberOfSections(in collectionView: NSCollectionView) -> Int {
switch state {
case .none:
return 0
case .requests(_), .searched(_):
return 1
case .searchedAndRequests(_, _):
return 2
}
}
}

Empty Collection View Swift

I followed 1 tutorial and i was able to fill a collectionView with some data(imageview and text):
let appleProducts = ["A", "B", "C", "D"]
let imageArray = [UIImage(named: "pug1"), UIImage(named: "pug2"), UIImage(named: "pug3"), UIImage(named: "pug4")]
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
return appleProducts.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath as IndexPath) as! CollectionViewCell
cell.imageView?.image = self.imageArray[indexPath.row]
cell.title?.text = self.appleProducts[indexPath.row]
return cell
}
Now passing from the demo project to mine, I want to fill this CollectionView with data(Picture and text) that I get from FirebaseDatabse so I created this method:
struct item {
let pictureId: String!
let picture: String!
}
var items = [item]()
func getLatestAddedItems(){
self.items.removeAll()
let databaseRef = FIRDatabase.database().reference()
databaseRef.child("Items").observe(.value, with: {
snapshot in
//self.items.insert(item(picture: picture), at: 0)
for childSnap in snapshot.children.allObjects {
let snap = childSnap as! FIRDataSnapshot
//print(snap.key)
let picture = (snap.value as? NSDictionary)?["bookImage"] as? String ?? ""
//print(picture)
self.items.append(item(pictureId:snap.key, picture:picture))
}
print(self.items.count)
})
}
And I create this button to call GetLatestAddedItems Method:
#IBAction func selectAction(_ sender: AnyObject) {
getLatestAddedItems()
}
And this one to check results:
#IBAction func gettableAction(_ sender: AnyObject) {
print(self.items[0].picture)
print(self.items[1].picture)
print(self.items[2].picture)
print(self.items.count) }
OutPut results:
picture 1 link
picture 2 link
picture 3 link
3
Everythings look fine and correct, now after making required changes in ContentView methods:
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 IndexPath) as! CollectionViewCell
//cell.imageView?.image = self.imageArray[indexPath.row]
let url = NSURL(string: items[indexPath.row].picture)
let data = NSData(contentsOf: url! as URL)
cell.imageView?.image = UIImage(data: data! as Data)
cell.title?.text = self.items[indexPath.row].pictureId
return cell
}
Now I'm getting an empty ContentView, the first time with button it works because I call the getLatestAddedItems() that will get and add data to the Items table, I try to call it in both ViewWillAppear or Viewdidload but nothings changes.
This is what I think the return items.count is returning 0 so nothings will appear any suggestions ?
Move your collectionView's protocol delegation initialisation to one of the ViewController lifecycle scope such as viewDidLoad() or viewWillAppear(_animated : Bool) if you are using a custom viewController(i.e embed a collectionView inside a viewController)
And reload your collectionView every time your user receives a value from its database.
override func viewDidLoad(){
super.viewDidLoad()
self.collectionView.dataSource = self
self.collectionView.delegate = self
}
func getLatestAddedItems(){
self.items.removeAll()
let databaseRef = FIRDatabase.database().reference()
databaseRef.child("Items").observe(.childAdded, with: {
snapshot in
for childSnap in snapshot.children.allObjects {
let snap = childSnap as! FIRDataSnapshot
let picture = (snap.value as? NSDictionary)?["bookImage"] as? String ?? ""
self.items.append(item(pictureId:snap.key, picture:picture))
print(self.items.count)
self.collectionView.reloadData()
}
})
}
PS:- All the calls to your firebase server are asynchronous, which takes some time to retrieve the data from your FBDB, so put print(self.items.count) should be inside the completionBlock of the firebase observing call otherwise if it is put outside it will be called even before your data has been retrieved from FBDB.