I am trying to display images onto a collection view. The image url's are saved in the realtime database in Firebase and was wondering, what I am doing wrong? Essentially, users have a node called 'Images' where images they have uploaded are saved inside that node. The names depend on the image Title and the url is the image they have uploaded. Below is my code and a photo of how my database looks like it. If someone could help me out, I would appreciate that a lot. Thanks!
The Issue is that the images are not being displayed. It is successfully able to display the correct amount of images the user has in their account but does not read the image url. My guess is that because each url is under a different value (imageTitle). Any ideas?
My ViewController:
class PicturesViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
#IBOutlet weak var imageCollection: UICollectionView!
var customImageFlowLayout: CustomImageFlowLayout!
var images = [UserImages]()
var dbRef: DatabaseReference!
override func viewDidLoad() {
super.viewDidLoad()
dbRef = Database.database().reference().child("users/\(Auth.auth().currentUser!.uid)/Images")
loadDB()
}
func loadDB() {
dbRef.observe(DataEventType.value, with: { (snapshot) in
var newImages = [UserImages]()
for UserImagesSnapshot in snapshot.children {
let UserImagesObject = UserImages(snapshot: UserImagesSnapshot as! DataSnapshot)
newImages.append(UserImagesObject)
}
self.images = newImages
self.imageCollection.reloadData()
})
}
func collectionView(_ imageCollection: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return images.count
}
func collectionView(_ imageCollection: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = imageCollection.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! ImageCollectionViewCell
let image = images[indexPath.row]
cell.imageView.sd_setImage(with: URL(string: image.url), placeholderImage: UIImage(named: "image1"))
return cell
}
My Array - UserImages
struct UserImages {
let key: String!
let url: String!
let itemsRef: DatabaseReference!
init (url:String, key:String) {
self.key = key
self.url = url
self.itemsRef = nil
}
init(snapshot: DataSnapshot) {
key = snapshot.key
itemsRef = snapshot.ref
let snapshotValue = snapshot.value as? NSDictionary
if let imageUrl = snapshotValue?[""] as? String {
url = imageUrl
} else {
url = ""
}
}
}
Replace
let snapshotValue = snapshot.value as? NSDictionary
if let imageUrl = snapshotValue?[""] as? String {
url = imageUrl
} else {
url = ""
}
with
url = snapshot.value as! String
Related
I'm having trouble displaying all of the followers of user on a table view cell with their profile picture and full name (similar to instagram).
A snippet of my firebase JSON structure is:
"followers" : {
"FoFQDAGGX9hntBiBdXYCBHd8yas2" : {
"CjeP35ceAQZJuUPhm7U1eF3Yq4F3" : true,
"FjS4wUpXAUa5aWwXkjvujHxE4He2" : true,
"Gmg1ojNoBiedFPRNSL4sBZz2gSx2" : true,
"PqMkClaPM3W8k7ZSgzAHb3yne5D3" : true,
"buS4recuDpdg60ckFqwjoU344TC2" : true
},
"users" : {
"CjeP35ceAQZJuUPhm7U1eF3Yq4F3" : {
"email" : "bbbb#gmail.com",
"fullname" : "Bbbb",
"profileImageUrl" : "https://firebasestorage.googleapis.com/v0/b/pinion-4896b.appspot.com/o/profile_image%2FCjeP35ceAQZJuUPhm7U1eF3Yq4F3?alt=media&token=0449c633-b397-4452-b2df-41f3a5390084",
"work" : "Nottingham",
},
Code in the table view cell (FollowersTableViewCell):
#IBOutlet weak var followersProfileImage: UIImageView!
#IBOutlet weak var followersNameLabel: UILabel!
var user: UserModel? {
didSet {
updateView()
}
}
func updateView() {
followersNameLabel.text = user?.fullname
if let photoUrlString = user?.profileImageUrl {
let photoUrl = URL(string: photoUrlString)
followersProfileImage.sd_setImage(with: photoUrl, placeholderImage: UIImage(named: "placeholderImg"))
}
}
EDIT:
Code in view controller (FollowersViewController)
#IBOutlet weak var tableView: UITableView!
var users: [UserModel] = []
func loadusers() {
let ref = Database.database().reference()
guard let currentUser = Auth.auth().currentUser?.uid else { return }
var followersNames = [String]()
var profileImage = [String]()
let followersRef = ref.child("followers").child(currentUser) //retreives all nodes in the following node
followersRef.observe(DataEventType.value, with: { snapshot in
print(snapshot.children.allObjects)
for child in snapshot.children { //build the array of keys
let snap = child as! DataSnapshot
let key = snap.key
let userRef = ref.child("users").child(key) //get the user name and profile image from the users node
userRef.observeSingleEvent(of: .value, with: { snapshot in
let followersName = snapshot.childSnapshot(forPath: "fullname").value as! String
let followersProfileImageUrl = snapshot.childSnapshot(forPath: "profileImageUrl").value as! String
print(followersName)
print(followersProfileImageUrl)
followersNames.append(followersName)
profileImage.append(followersProfileImageUrl)
self.tableView.reloadData()
})
}
})
}
extension FollowersViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return users.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "FollowersTableViewCell", for: indexPath) as! FollowersTableViewCell
let user = users[indexPath.row]
cell.user = user
return cell
}
}
Now the code runs and the profile picture and fullname of the followers are printed on the console but doesn't show anything on the table view of the app - thanks in advance :)
Update:
User model definition
class UserModel {
var email: String?
var work: String?
var profileImageUrl: String?
var fullname: String?
var id: String?
}
extension UserModel {
static func transformUser(dict: [String: Any], key: String) -> UserModel {
let user = UserModel()
user.email = dict["email"] as? String
user.work = dict["work"] as? String
user.profileImageUrl = dict["profileImageUrl"] as? String
user.fullname = dict["fullname"] as? String
user.id = key
return user
}
}
Your TableView does not display any data because you don't populate users array at any point.
I might want to instantiate an UserModel object in observeSingleEvent implementation, add the object to users array and invoke reloadData (or insertRows) method also right after that. (Instead of outside the implementation block)
As requested, here is a quick (and dirty) way to create an user object and refresh the UI
let user = UserModel()
user.fullname = snapshot.childSnapshot(forPath: "fullname").value as? String
user.profileImageUrl = snapshot.childSnapshot(forPath: "profileImageUrl").value as? String
self.users.append(user)
self.tableView.reloadData()
I am building an app which uses Firebase's database service. I am trying to load the data into a table view but I am unable to do so. I can't seem to figure out what's going wrong. The code is also not giving me any errors. I've checked the database permissions on Firebase and they seem to be good. Here's my code:
import UIKit
import Firebase
struct postStruct {
let word : String!
let wordType : String!
}
class sentenceBuilderViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var wordSearchBar: UISearchBar!
#IBOutlet weak var wordsTableView: UITableView!
var posts = [postStruct]()
override func viewDidLoad() {
wordsTableView.reloadData()
getWordsFromDatabase()
super.viewDidLoad()
wordsTableView.delegate = self
wordsTableView.dataSource = self
}
func getWordsFromDatabase() {
let databaseRef = Database.database().reference()
databaseRef.child("wordList").queryOrderedByKey().observeSingleEvent(of: .childAdded, with: {
snapshot in
let word = (snapshot.value as? NSDictionary)!["word"] as? String
let wordType = (snapshot.value as? NSDictionary
)!["wordType"] as? String
self.posts.insert(postStruct(word: word, wordType: wordType), at: 0)
})
wordsTableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return posts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = wordsTableView.dequeueReusableCell(withIdentifier: "Cell")
let wordLabel = cell?.viewWithTag(1) as! UILabel
wordLabel.text = posts[indexPath.row].word
let wordTypeLabel = cell?.viewWithTag(2) as! UILabel
wordTypeLabel.text = posts[indexPath.row].wordType
return cell!
}
}
Any help and inputs would be appreciated! Thanks!
The problem is that you are just observing a single event here:
databaseRef.child("wordList").queryOrderedByKey().observeSingleEvent(of: .childAdded, with: {
snapshot in
What this does is that it justs goes through your database and once it finds any child, it displays that one without going further. What you need to do is change it to observe like this:
func getAllWordsFromDatabase() {
let databaseRef = Database.database().reference()
databaseRef.child("wordList").queryOrderedByKey().observe(.childAdded, with: {
snapshot in
let word = (snapshot.value as? NSDictionary)!["word"] as? String
let wordType = (snapshot.value as? NSDictionary)!["wordType"] as? String
self.posts.append(postStruct(word: word, wordType: wordType))
DispatchQueue.main.async {
self.wordsTableView.reloadData()
}
})
}
Try implementing this and it should work.
Move the "getWordsFromDatabase()" line in "viewDidLoad" function to AFTER you assign the delegate and data source, like this:
override func viewDidLoad() {
super.viewDidLoad()
wordsTableView.delegate = self
wordsTableView.dataSource = self
getWordsFromDatabase()
}
Also you can try to add a "reloadData()" method in the databaseRef block on the main queue, like this:
let databaseRef = Database.database().reference()
databaseRef.child("wordList").queryOrderedByKey().observeSingleEvent(of: .childAdded, with: {
snapshot in
let word = (snapshot.value as? NSDictionary)!["word"] as? String
let wordType = (snapshot.value as? NSDictionary
)!["wordType"] as? String
self.posts.insert(postStruct(word: word, wordType: wordType), at: 0)
DispatchQueue.main.async {
wordsTableView.reloadData()
}
})
I Have boiled down the problem to this
this closure :
override func viewDidLoad() {
super.viewDidLoad()
let data = homeDataSource()
getPrivatePosts { (posts) in
print("postsCOUNT" , posts!.count)
data.posts = posts!
}
self.datasource = data
collectionView?.reloadData()
}
prints out "postCOUNT 1 postCOUNT 3"
then when I print the count of data.posts I get 0... whats going on with that?? here is the full code
this is a custom UICollectionView:
import LBTAComponents
import Firebase
class homeView: DatasourceController {
override func viewDidLoad() {
super.viewDidLoad()
let data = homeDataSource()
getPrivatePosts { (posts) in
print("postsCOUNT" , posts!.count)
data.posts = posts!
}
self.datasource = data
collectionView?.reloadData()
}
override func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width , height: 150)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return CGSize(width: view.frame.width, height: 0 )
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize {
return CGSize(width: view.frame.width, height: 0)
}
// just to test
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
performSegue(withIdentifier: "goToNewPost", sender: self)
}
func getPrivatePosts(completion : #escaping (_ privatePosts : [Post]?) ->()){
// fill posts array with posts from all buddys "privataPosts only"
var ret = [Post]()
staticValuesForData.instance.dataBaseUserref.child((Auth.auth().currentUser?.uid)!).child("contacts").observe( .value , with: { (snapshot) in
let dict = snapshot.children.allObjects as! [DataSnapshot]
for d in dict{
if let contactUid = d.childSnapshot(forPath: "uid").value as? String{
staticValuesForData.instance.dataBaseUserref.child(contactUid).child("privatePosts").observe( .value, with: { (snapshot) in
let posts = snapshot.children.allObjects as! [DataSnapshot]
print("postval" , posts)
for post in posts{
if let dict = post.value as? [String : AnyObject]{
let fullname = dict["fullname"] as! String
let picUrl = dict["picUrl"] as! String
let postContent = dict["postContent"] as! String
let time = dict["time"] as! Int
let uid = dict["uid"] as! String
let username = dict["username"] as! String
print("first name of person who did the post" , fullname)
let reposts = dict["reposts"] as! [String]
let downs = dict["downs"] as! [String]
// possible issue
var comments = [Comment]()
let commentArr = snapshot.childSnapshot(forPath: "comments").children.allObjects as! [DataSnapshot]
for c in commentArr{
if let dict = c.value as? [String : AnyObject]{
let cuid = dict["uid"] as! String
let ccommentText = dict["commentText"] as! String
let cpicUrl = dict["picUrl"] as! String
let cusername = dict["username"] as! String
let ctime = dict["time"] as! Int
let com = Comment(uid: cuid, commentText: ccommentText, time: ctime, picUrl: cpicUrl, username: cusername)
comments.append(com)
}
}
print("HERE : post content\(postContent) username : \(username) commentArr \(comments)")
let postToAdd = Post(postContent: postContent, picUrl: picUrl, userName: username, fullName: fullname, postID: uid, postTime: time, downs: downs, reposts: reposts, comments: comments)
print("LOOK AT MEE \(postToAdd.userName) is the username of the post object \(postToAdd.postContent) is the contetn")
ret.append(postToAdd)
print("RET" , ret)
}
}
completion(ret) // this is where the completion block should be called
})
}
}
})
}
}
This is a datasource object :
import LBTAComponents
class homeDataSource: Datasource {
var posts = [Post]()
override func numberOfItems(_ section: Int) -> Int {
print("COUNT " , posts.count)
return posts.count
}
override func headerClasses() -> [DatasourceCell.Type]? {
return [userHeader.self]
}
override func footerClasses() -> [DatasourceCell.Type]? {
return [userFooter.self]
}
override func cellClasses() -> [DatasourceCell.Type] {
return [userCell.self]
}
override func item(_ indexPath: IndexPath) -> Any? {
return posts[indexPath.item]
}
}
The frame work can be used here :
pod 'LBTAComponents'
You have the same basic misunderstanding twice.
In the second code section you create your ret variable initially empty and then fire of some async tasks. However you call the completion(ret) outside of the async task so it will fire immediately before the async tasks have finished and therefore return your initial empty value.
The fist code also will suffer the same problem in that you create your postArray initially empty then call your getPrivatePosts function supplying a completion handler but that completion handler will be called in an async task so there could be a delay but you use the value immediately and therefore will return the empty initial value.
You shouldn't create your posts array in that way. You should create the a mutable array:
var posts = [Post]()
Then in the viewDidLoad of your view controller you should then populate the array from your service (Firebase).
override func viewDidLoad() {
super.viewDidLoad()
getPrivatePosts() { posts in
self.posts = posts ?? []
}
}
You posts function also is never going to return the data you want from the service since your calling your completion block outside of the scope of the service request. Move the completion block to the bottom of the for loop in the staticValuesForData.instance.dataBaseUserref.child part of the getPrivatePosts function like so:
class func getPrivatePosts(completion : (_ privatePosts : [Post]?) ->. ()){
// fill posts array with posts from all buddys "privataPosts only"
var ret = [Post]()
staticValuesForData.instance.dataBaseUserref.child((Auth.auth().currentUser?.uid)!).child("contacts").observe( .value , with: { (snapshot) in
let dict = snapshot.children.allObjects as! [DataSnapshot]
for d in dict{
if let contactUid = d.childSnapshot(forPath: "uid").value as? String{
staticValuesForData.instance.dataBaseUserref.child(contactUid).child("privatePosts").observe( .value, with: { (snapshot) in
let posts = snapshot.children.allObjects as! [DataSnapshot]
print("postval" , posts)
for post in posts{
if let dict = post.value as? [String : AnyObject]{
let fullname = dict["fullname"] as! String
let picUrl = dict["picUrl"] as! String
let postContent = dict["postContent"] as! String
let time = dict["time"] as! Int
let uid = dict["uid"] as! String
let username = dict["username"] as! String
print("first name of person who did the post" , fullname)
let reposts = dict["reposts"] as! [String]
let downs = dict["downs"] as! [String]
// possible issue
var comments = [Comment]()
let commentArr = snapshot.childSnapshot(forPath: "comments").children.allObjects as! [DataSnapshot]
for c in commentArr{
if let dict = c.value as? [String : AnyObject]{
let cuid = dict["uid"] as! String
let ccommentText = dict["commentText"] as! String
let cpicUrl = dict["picUrl"] as! String
let cusername = dict["username"] as! String
let ctime = dict["time"] as! Int
let com = Comment(uid: cuid, commentText: ccommentText, time: ctime, picUrl: cpicUrl, username: cusername)
comments.append(com)
}
}
print("HERE : post content\(postContent) username : \(username) commentArr \(comments)")
let postToAdd = Post(postContent: postContent, picUrl: picUrl, userName: username, fullName: fullname, postID: uid, postTime: time, downs: downs, reposts: reposts, comments: comments)
print("LOOK AT MEE \(postToAdd.userName) is the username of the post object \(postToAdd.postContent) is the contetn")
ret.append(postToAdd)
print("RET" , ret)
}
}
completion(ret) // this is where the completion block should be called
})
}
}
})
}
I hope this helps.
How do I properly load the array (image, image2, image3) images from my class Model in collectionCell?
The class itself Model looks like this:
class Model {
var image: String
var image2: String
var image3: String
var images: [String] = []
var images2: [String] = []
var images3: [String] = []
var ref: FIRDatabaseReference!
init(snapshot: FIRDataSnapshot) {
ref = snapshot.ref
let value = snapshot.value as! NSDictionary
let snap = value["hall1"] as? NSDictionary
let snap2 = value["hall2"] as? NSDictionary
let snap3 = value["hall3"] as? NSDictionary
image = snap?["qwerty"] as? String ?? ""
image2 = snap2?["qwerty"] as? String ?? ""
image3 = snap3?["qwerty"] as? String ?? ""
if let post1 = snap as? [String: AnyObject] {
for (_, value) in post1["images"] as! [String: AnyObject] {
self.images.append(value as! String)
}
}
if let post2 = snap2 as? [String: AnyObject] {
for (_, value) in post2["images"] as! [String: AnyObject] {
self.images2.append(value as! String)
}
}
if let post3 = snap3 as? [String: AnyObject] {
for (_, value) in post3["images"] as! [String: AnyObject] {
self.images3.append(value as! String)
}
}
}
}
In my collectionCell loaded only the first image, I basically understand why, as I understand it is due to the fact that sd_setImage does not display arrays (correct me if I'm wrong), but how to fix it can not figure out.
Code of the collectionCell:
class CollectionViewCell11: UICollectionViewCell, UICollectionViewDelegate, UICollectionViewDataSource {
var imagess: [Model] = []
#IBOutlet weak var collectionView: UICollectionView!
var vc1: ViewController?
override func awakeFromNib() {
super.awakeFromNib()
collectionView.delegate = self
collectionView.dataSource = self
}
func collectionView(_ collectionView: UICollectionView, numb erOfItemsInSection section: Int) -> Int {
return imagess.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell12
cell.imageView.sd_setImage(with: URL(string: imagess[indexPath.item].image))
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if vc1 != nil {
let vc2 = vc1!.storyboard?.instantiateViewController(withIdentifier: "ViewController2") as! ViewController2
vc2.photo = [imagess[indexPath.item]]
let backItem = UIBarButtonItem()
backItem.title = ""
vc1!.navigationItem.backBarButtonItem = backItem
vc1!.navigationController?.pushViewController(vc2, animated: true)
}
}
}
To use I need only the class Model, because it contains another array of images which I plan to display the next controller for scroll image.
If your next ViewController is a child of this cell then you can pass the array on to it this way.
You write this in the viewDidLoad() of the next ViewController:
var newImagesArray: [Model] = []
if let parentVC = self.parent as? CollectionViewCell11 {
newImagesArray = parentVC.imagess
}
I have the following JSON Firebase database:
{ "fruits": {
"apple": {
"name": "Gala",
"url": "//s-media-cache-ak0.pinimg.com/564x/3e/b1/e7/3eb1e756d66856975d6e43ebb879200a.jpg",
"fruitArray": [1, 2]
},
"orange": {
"name": "Tangerine",
"url": "//userscontent2.emaze.com/images/0ba588c8-42d9-45e9-a843-d19e5720515a/e430f9a827f139e9f99f2826175dd0a9.jpg",
"fruitArray": []
}
}
}
the following Fruit class:
class Fruit {
var name: String
var url: String
var fruitArray: [Int]
var ref: FIRDatabaseReference?
init(name: String, url: String, fruitArray: [Int]) {
self.name = name
self.url = url
self.fruitArray = fruitArray
self.ref = nil
}
init(snapshot: FIRDataSnapshot) {
let snapshotValue = snapshot.value as! [String: Any]
name = snapshotValue["name"] as! String
url = snapshotValue["url"] as! String
if snapshotValue["fruitArray"] == nil {
fruitArray = [0]
} else {
fruitArray = snapshotValue["fruitArray"] as! [Int]
}
ref = snapshot.ref
}
func toAnyObject() -> Any {
return [
"name": name,
"url": url,
"fruitArray": fruitArray
]
}
And the following FruitTableViewController Code:
class FruitTableViewController: UITableViewController {
// MARK: Properties
var fruits: [Fruit] = []
override func viewDidLoad() {
super.viewDidLoad()
let ref = FIRDatabase.database().reference(withPath: "fruits")
ref.queryOrdered(byChild: "name").observe(.value, with: { snapshot in
var addedFruits: [Fruit] = []
for fruit in snapshot.children {
let newFruit = Fruit(snapshot: fruit as! FIRDataSnapshot)
addedFruit.append(newFruit)
}
self.fruits = addedFruits
self.tableView.reloadData()
})
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return fruits.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? FruitTableViewCell
let fruit = fruits[indexPath.row]
let imgURL = NSURL(string: fruit.url)
if imgURL != nil {
let data = NSData(contentsOf: (imgURL as? URL)!)
cell.icon.image = UIImage(data: data as! Data)
}
cell.nameLabel.text = fruit.name
return cell
}
For some reason, the Firebase snapshot is not working. I've tried almost everything with no luck.
It's not a TableViewCell issue (I think) because I checked the FruitViewCell and Storyboard and everything is in order. My hunch is that it has something to do with the way I'm changing the URL to a string as well as the array. I've used this exact code for a different iOS project and it worked but the difference between the two projects is that this one has an array and link within the JSON while the other one didn't.
I've seen that there are other ways to take a snapshot but I'm going to use the fruit data throughout the app and thus it's easier for me to have a Fruit object, but I wouldn't mid if someone were to suggest an alternate way of taking a snapshot that works. Any help is appreciated!
First of all change your viewDidLoad with this code as addedFruites is not needed at all
override func viewDidLoad() {
super.viewDidLoad()
let ref = FIRDatabase.database().reference().child("fruits")
ref.observe(.value, with: { snapshot in
if snapshot.exists() {
for fruit in snapshot.children {
let newFruit = Fruit(snapshot: fruit as! FIRDataSnapshot)
self.fruits.append(newFruit)
}
self.tableView.reloadData()
}
})
}
check firebase rules for read and write is properly set or not.. I think here is an issue because may be you did not set that rules.