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
}
}
}
Related
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()
}
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!
I've a collectionview inside my resizable tablviewCells. Tableview has one cell in each 'n' number of sections. Datasource and delegate of collectionview are set to the tableviewCell. There is an API called on tablview's cellForRowAt, and the result is rendered on the collectionview for each cell. After the result is fetched, a delegate tells the tableview that collectionview is loaded and it should reload that cell without calling the API this time. But the problem is that my collectionview data is repeated after every 2 tableviewCells.
I know prepareForReuse should be override to get rid of cell reuse problems. I've implemented prepareForReuse in my collectionviewCells and set my label.text and imageView.image to nil. However i'm not sure what to add to prepareForReuse for my tableviewCell.
// TableView class
override func viewDidLoad() {
super.viewDidLoad()
storiesSections = [....]
tableView.register(UINib(nibName: "RWFeedTableViewCell", bundle: nil), forCellReuseIdentifier: "reuseIdentifier")
tableView.estimatedRowHeight = 1
tableView.rowHeight = UITableView.automaticDimension
}
override func numberOfSections(in tableView: UITableView) -> Int {
return storiesSections.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath) as! RWFeedTableViewCell
if cell == nil {
cell = UITableViewCell(style: UITableViewCell.CellStyle.default, reuseIdentifier: "reuseIdentifier") as! RWFeedTableViewCell
}
cell.delegate = self
cell.fetchData(feedSection: storiesSections[indexPath.section], indexPath: indexPath)
return cell
}
// delegate for tableview reload
func collectionViewDidEnd(updatedFeedSection: FeedSection, indexPath: IndexPath) {
storiesSections[indexPath.section] = updatedFeedSection
tableView.beginUpdates()
tableView.endUpdates()
}
// TableViewCell class
override func awakeFromNib() {
super.awakeFromNib()
initializeCode()
}
func initializeCode() {
// Set layout
self.collectionView.collectionViewLayout = RWWaterfallLayout2()
self.collectionView.register(UINib(nibName: "\(ImageThenTitleViewCell.self)", bundle: nil), forCellWithReuseIdentifier: kImageThenTitleCellID)
self.collectionView.register(UINib(nibName: "\(LeftImageCell.self)", bundle: nil), forCellWithReuseIdentifier: kLeftImageCellID)
self.collectionView.contentInset = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)
self.collectionView.isScrollEnabled = false
self.collectionView.dataSource = self
self.collectionView.delegate = self
}
func fetchData(feedSection: FeedSection, indexPath: IndexPath) {
if feedSection.isLoadComplete {
return
}
if let catID = feedSection.categoryID {
let urlString = URL(string: <urlString>)
let urlRequest = URLRequest(url: urlString!)
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
let task = session.dataTask(with: urlRequest) { (data, response, error) in
if error == nil {
} else {
print(error?.localizedDescription as Any)
}
guard let responseData = data else {
print("Error: did not receive data")
return
}
do {
guard let todo = try JSONSerialization.jsonObject(with: responseData, options: [])as? [String: Any] else {
print("error trying to convert data to JSON")
return
}
print("success convert data to JSON")
DispatchQueue.main.async {
var updatedFeedSection = feedSection
updatedFeedSection.storiesArray? = (todo["data"]! as! Array)
updatedFeedSection.isLoadComplete = true
self.feedSection = updatedFeedSection
self.collectionView.reloadData()
self.collectionView.performBatchUpdates({
}, completion: { (complete) in
self.collectionViewHeightConstraint.constant = self.collectionView.collectionViewLayout.collectionViewContentSize.height + self.collectionView.contentInset.top + self.collectionView.contentInset.bottom
self.delegate?.collectionViewDidEnd(updatedFeedSection: updatedFeedSection, indexPath: indexPath)
})
}
} catch {
print("error trying to convert data to JSON")
return
}
}
task.resume()
}
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if self.feedSection == nil {
return 0
} else {
return (self.feedSection?.storiesArray?.count)!
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let indexForTen = indexPath.item%10
let story = self.feedSection?.storiesArray?[indexPath.item]
if indexForTen == 0 {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: kImageThenTitleCellID, for: indexPath) as! ImageThenTitleViewCell
cell.setupData(story: story!)
return cell
}
else {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: kLeftImageCellID, for: indexPath) as! LeftImageCell
cell.setupData(story: story!)
return cell
}
}
override func prepareForReuse() {
super.prepareForReuse()
}
// Collectionview Cell
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
func setupData(story: Dictionary<String, Any>){
self.storyImage.image = nil // reset the image
let thumbImage = story["image"] as! Dictionary<String, String>
self.storyTitle.text = story["t"] as? String
self.storyImage.downloaded(from: (thumbImage["m"])!)
self.layer.borderColor = UIColor.lightGray.cgColor
self.layer.borderWidth = 1
self.layer.cornerRadius = 8
}
override func prepareForReuse() {
super.prepareForReuse()
storyImage.image = nil
storyTitle.text = nil
}
// FeedSection struct
struct FeedSection {
var categoryID: String?
var storiesArray : [Dictionary<String, Any>]?
var isLoadComplete: Bool
init(categoryID: String) {
self.categoryID = categoryID
self.storiesArray = []
self.isLoadComplete = false
}
}
Currently the 3rd tableviewCell repeats the data of 1st tablviewCell. How to avoid repeating cell data?
Only problem was the feedSection object in TableViewCell. It should be initialized at the time fetchData() is called. And just reload the collectionView if isLoadComplete is true.
Also since isLoadComplete is set on completion handler of URLSession, I set it to true the time API is called. So the same api will not be called while waiting for response. Maybe an enum could be set for api call and api response events on FeedSection. But for now this works.
func fetchData(feedSection: FeedSection, indexPath: IndexPath) {
self.feedSection = feedSection
if self.feedSection.isLoadComplete {
self.collectionView.reloadData()
return
}
if let catID = feedSection.categoryID {
let urlString = URL(string: <urlString>)
let urlRequest = URLRequest(url: urlString!)
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
self.feedSection.isLoadComplete = true
self.delegate?.collectionViewDidEnd(updatedFeedSection: self.feedSection, indexPath: indexPath)
let task = session.dataTask(with: urlRequest) { (data, response, error) in
if error == nil {
} else {
print(error?.localizedDescription as Any)
}
guard let responseData = data else {
print("Error: did not receive data")
return
}
do {
guard let todo = try JSONSerialization.jsonObject(with: responseData, options: [])as? [String: Any] else {
print("error trying to convert data to JSON")
return
}
print("success convert data to JSON")
DispatchQueue.main.async {
self.feedSection.storiesArray? = (todo["data"]! as! Array)
self.feedSection.isLoadComplete = true
self.collectionView.reloadData()
self.collectionView.performBatchUpdates({
}, completion: { (complete) in
self.collectionViewHeightConstraint.constant = self.collectionView.collectionViewLayout.collectionViewContentSize.height + self.collectionView.contentInset.top + self.collectionView.contentInset.bottom
self.delegate?.collectionViewDidEnd(updatedFeedSection: self.feedSection, indexPath: indexPath)
})
}
} catch {
print("error trying to convert data to JSON")
return
}
}
task.resume()
}
}
Set UITableView.reloadData() after get the data.
And check if you set CollectionView.reloadData(), if yes then remove reloadData() of UICollectionView. Only set UITableView.reloadData()
I am having trouble sending the postid to comments vc so that I can save it in Firebase.
I am following a tutorial online where we are building this architecture on Firebase however when I send the data to Firebase it is not capturing the postId as seen onthis screenshot. I'm obviously missing something. Would really appreciate it if someone could shed some light as to what my mistake could be. Below I'll put the Comments View Controller function where I send the data to Firebase.
#IBAction func sendButtonPressed(_ sender: UIButton) {
let ref = Database.database().reference()
let commentsReference = ref.child("comments")
let newCommentId = commentsReference.childByAutoId().key
let newCommentReference = commentsReference.child(newCommentId)
//current user information
guard let currentUser = Auth.auth().currentUser else {
return
}
let currentUserId = currentUser.uid
newCommentReference.setValue(["userid": currentUserId, "commentText": commentTextField.text!]) { (error, ref) in
if error != nil {
ProgressHUD.showError(error!.localizedDescription)
return
}
let postCommentRef = Database.database().reference().child("post-comments").child(self.postIdNew).child(newCommentId)
postCommentRef.setValue(true, withCompletionBlock: { (error, ref) in
if error != nil {
ProgressHUD.showError(error!.localizedDescription)
return
}
})
self.empty()
self.view.endEditing(true)
}
}
This is how I'm supposed to get the postId reference from the Home View Controller.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "commentSegue" {
let commentVC = segue.destination as! CommentViewController
let postId = sender as! String
commentVC.postIdNew = postId
}
}
Here are my collection view extensions
extension HomeViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if collectionView == self.lostCollectionView {
return posts.count
}
if collectionView == self.foundCollectionView {
return newPostsFound.count
}
if collectionView == self.adoptionCollectionView {
return postsadoption.count
}
else {
return 0
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
switch collectionView {
case lostCollectionView:
let lostcell: LostCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "Lostcell", for: indexPath) as! LostCollectionViewCell
let post = posts[indexPath.row]
let user = users[indexPath.row]
lostcell.post = post
lostcell.user = user
//Make TextView Clickable
lostcell.phoneLostTextView.isEditable = false;
lostcell.phoneLostTextView.dataDetectorTypes = UIDataDetectorTypes.phoneNumber
//Make Comments View Clickable
lostcell.homeVC = self
return lostcell
case foundCollectionView:
let foundcell: FoundCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "Foundcell", for: indexPath) as! FoundCollectionViewCell
let post = newPostsFound[indexPath.row]
foundcell.post = post
//Make TextView Clickable
foundcell.phoneFoundTextView.isEditable = false;
foundcell.phoneFoundTextView.dataDetectorTypes = UIDataDetectorTypes.phoneNumber
return foundcell
case adoptionCollectionView:
let adoptioncell: AdoptionCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "Adopotioncell", for: indexPath) as! AdoptionCollectionViewCell
let post = postsadoption[indexPath.row]
adoptioncell.post = post
//Make TextView Clickable
adoptioncell.phoneAdoptionTextView.isEditable = false;
adoptioncell.phoneAdoptionTextView.dataDetectorTypes = UIDataDetectorTypes.phoneNumber
return adoptioncell
default:
return UICollectionViewCell()
}
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
switch collectionView {
case lostCollectionView:
let vc = storyboard?.instantiateViewController(withIdentifier: "lostSelectedViewController") as? LostSelectedViewController
self.navigationController?.pushViewController(vc!, animated: true)
vc?.posts = posts[indexPath.row]
break
case foundCollectionView:
let vc = storyboard?.instantiateViewController(withIdentifier: "foundSelectedViewController") as? FoundSelectedViewController
self.navigationController?.pushViewController(vc!, animated: true)
vc?.posts = newPostsFound[indexPath.row]
break
case adoptionCollectionView:
let vc = storyboard?.instantiateViewController(withIdentifier: "adoptionSelectedViewController") as? AdoptionSelectedViewController
self.navigationController?.pushViewController(vc!, animated: true)
vc?.posts = postsadoption[indexPath.row]
break
default:
break
}
func commentsViewPressed() {
print("Hola")
performSegue(withIdentifier: "commentSegue", sender: self)
}
}
}
The issue looks like its in this code
let postId = sender as! String
commentVC.postIdNew = postId
Sender refers to the object that initiated the segue, so likely a button or TableViewCell (see apples docs: https://developer.apple.com/documentation/uikit/uiviewcontroller/1621490-prepare)
You need to replace sender as! String with the value of the postId that you are trying to set. I assume that you can get that from somewhere on the first view controller.
Actually my mistake was nowhere here. My mistake was that my snapshot.key was not retreiving the childByAutoId so when I ran the query it gave me a list of matching children and since there was no single result, the SDK prints the key of the location/collection that you queried: posts.
All I had to do was to retrieve the childByAutoId I had to use this line:
let key = childSnapshot.key as String
I found my answer in this stackoverflow question --> Firebase snapshot.key not returning actual key?
I have an iOS app, written in swift. It's a social platform where the users can post 9 different types of posts (text, image, video, link, audio, poll, chat, etc). I set those using an enum
enum PostType: String {
case image = "image"
case gif = "gif"
case video = "video"
case text = "text"
case link = "link"
case audio = "audio"
case poll = "poll"
case chat = "chat"
case quote = "quote"
}
I'm utilising FirebaseDatabase to store the data. In the DashboardViewController I query the database and get the posts in an array along with the corresponding users, ready to be displayed.
func loadPosts() {
activityIndicator.startAnimating()
Api.Feed.observeFeedPosts(withUserId: Api.Users.CURRENT_USER!.uid) {
post in
guard let userId = post.userUid else { return }
self.fetchUser(uid: userId, completed: {
self.posts.insert(post, at: 0)
self.activityIndicator.stopAnimating()
self.collectionView.reloadData()
})
}
Api.Feed.observeFeedRemoved(withUserId: Api.Users.CURRENT_USER!.uid) { (post) in
self.posts = self.posts.filter { $0.id != post.id } // removed all array elements matching the key
self.users = self.users.filter { $0.id != post.userUid }
self.collectionView.reloadData()
}
}
func fetchUser(uid: String, completed: #escaping () -> Void ) {
Api.Users.observeUsersShort(withId: uid) {
user in
self.users.insert(user, at: 0)
completed()
}
}
Whenever the user creates a new post, it stores PostType.text.rawValue (for example, it gives "text" String) on the database to differentiate between them (either video, photo, text, etc). Now, I have to use the PostType enum to figure out what the post type is and display the corresponding UICollectionViewCell. Now, if its a single cell, it's easy. I can do this and it works:
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return posts.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CellName.postTextCVC, for: indexPath) as! PostTextCVC
let user = users[indexPath.row]
let post = posts[indexPath.row]
cell.delegatePostTextCVC = self
cell.user = user
cell.dashboardVC = self
cell.post = post
return cell
}
The problem is, how to use the enum to display the appropriate cell?
Keep a variable PostType variable in your Post class. In cellForItemAt check post type of the post and dequeue respective cell.
Something like this.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let post = posts[indexPath.row]
let type: PostType = post.type // Get the post type eg. text, image etc.
switch type {
case .text:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CellName.postTextCVC, for: indexPath) as! PostTextCVC
let user = users[indexPath.row]
cell.delegatePostTextCVC = self
cell.user = user
cell.dashboardVC = self
cell.post = post
return cell
case .image:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CellName.postImageCVC, for: indexPath) as! PostImageCVC
return cell
case .video:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CellName.postTextCVC, for: indexPath) as! PostVideoCVC
return cell
}
}
If you are using separate nib files for each collection view cell, make sure you register all possible nibs with collection view like this.
collectionView.register(UINib(nibName: "PostTextCVC", bundle: nil), forCellWithReuseIdentifier: CellName.postTextCVC)