Does someone know why this is not working? The tableView is empty and not showing anything even though there are items in the database and storage. This worked fine before I implemented the loading of the images from storage which you will see at the bottom of this code that I have pasted in. The food.append() statement used to be outside the storageRef.getData() closure (since it didn't exist) however if I take it out now it won't be able to access recipeImage since it's declared within the closure. Is it not working because it's in the closure? If so how do I fix it?
let parentRef = Database.database().reference().child("Recipes")
let storage = Storage.storage()
parentRef.observe(.value, with: { snapshot in
//Processes values received from server
if ( snapshot.value is NSNull ) {
// DATA WAS NOT FOUND
print("– – – Data was not found – – –")
} else {
//Clears array so that it does not load duplicates
food = []
// DATA WAS FOUND
for user_child in (snapshot.children) {
let user_snap = user_child as! DataSnapshot
let dict = user_snap.value as! [String: String?]
//Defines variables for labels
let recipeName = dict["Name"] as? String
let recipeDescription = dict["Description"] as? String
let downloadURL = dict["Image"] as? String
let storageRef = storage.reference(forURL: downloadURL!)
storageRef.getData(maxSize: 1 * 1024 * 1024) { (data, error) -> Void in
let recipeImage = UIImage(data: data!)
food.append(Element(name: recipeName!, description: recipeDescription!, image: recipeImage!))
}
}
self.tableView.reloadData()
}
})
Move
self.tableView.reloadData()
after
food.append(Element(name: recipeName!, description: recipeDescription!, image: recipeImage!))
Related
I have been searching for a while now, but I don't know how to deal with this problem.
I have a Firebase Database with some data and the links to images stored into the Storage.
I want my UICollectionView to be reloaded after I downloaded every single image from the database, and not only the first one.
newRef.observeSingleEvent(of: .value, with: { (snapshotOne) in
for child in snapshotOne.children {
let snap = child as! DataSnapshot
let key = snap.key
newRef.child(key).observeSingleEvent(of: .value, with: { (snapshotTwo) in
for child in snapshotTwo.children {
let snappotto = child as! DataSnapshot
var imageDownloaded: UIImage?
if let dictionary = snappotto.value as? [String : AnyObject] {
let url = URL(string: dictionary["imageURL"] as! String)
URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error != nil {
print("[Error]: \(String(describing: error))")
return
}
imageDownloaded = UIImage(data: data!)
let person = Person(name: dictionary["name"] as! String, surname: dictionary["surname"] as! String, tags: [.castana], image: imageDownloaded!)
self.storedData.append(person)
self.filteredData = self.storedData
DispatchQueue.main.sync {
UIApplication.shared.endIgnoringInteractionEvents()
self.collectionView.reloadData()
self.dismiss(animated: false, completion: nil)
}
}.resume()
}
}
}) { (error) in
print("[Error]: \(String(describing: error))")
}
}
}) { (error) in
print("[Error]: \(error)")
}
This is the code I use, but this updates my UICollectionView after only the first image is downloaded, and with that it endsIgnoringInteractionEvents - and allows the user to reload the data again and again and this causes a lot of images to duplicate.
How can I move the reloading and the endIgnoringInteractionEvents after every single item from my database is downloaded?
The database is structured like this:
| folder
-| user
--| autoId
---| name
---| surname
---| imageUrl
--|autoId
---| name
---| surname
---| imageUrl
-| user
--| autoId
---| name
---| surname
---| imageUrl
Thanks a lot, NicopDev
You're nesting observers on nested data, which seems like a waste of code. When you attach an observer to a location, all data under that location is loaded already.
So you can just loop over the nested snapshot to get the same result:
newRef.observeSingleEvent(of: .value, with: { (snapshot) in
for child in snapshot.children {
let userSnapshot = child as! DataSnapshot
let userKey = userSnapshot.key
for child in userSnapshot.children {
let imageSnapshot = child as! DataSnapshot
var imageDownloaded: UIImage?
...
With that out of the way, let's move on to your real question: how can you detect when all images have loaded.
One simple, cross-platform way to do this is by simply counting how many images you have loaded, and compare that to how many images you know exist. Since you have a tree of only existing images, you can do both in a iteration over the double nested structure
let knownImageCount = 0 // we start with no knowledge of any image
let loadedImageCount = 0 // we also haven't loaded any image yet
for child in snapshot.children {
let userSnapshot = child as! DataSnapshot
let userKey = userSnapshot.key
knownImageCount = knownImageCount + userSnapshot.childrenCount // we've found out about N more images
for child in userSnapshot.children {
let imageSnapshot = child as! DataSnapshot
var imageDownloaded: UIImage?
...
URLSession.shared.dataTask(with: url!) { (data, response, error) in
...
loadedImageCount = loadedImageCount + 1 // we loaded an additional image
if loadedImageCount == knownImageCount {
... // we've loaded all known images, we're done!
}
}.resume()
I want to get a saved Image in Firebase Storage.
I have the url saved in an string value in the Firebase database:
mediaUrl = url!.absoluteString
Now I want to get this image.
I observe the messages.
func observeMessages() {
let query = Constants.refs.databaseChats.child(chatId).queryLimited(toLast: 50)
_ = query.observe(.childAdded, with: { [weak self] snapshot in
if let data = snapshot.value as? [String: String],
let id = data["sender_id"],
let name = data["name"],
let timestamp = data["timestamp"],
let media = data["media"],
let text = data["text"]?.encode(),
!text.isEmpty
{
if media == "text" {
if let message = JSQMessage(senderId: id, senderDisplayName: name, date: self!.dateFormatter.date(from: timestamp), text: text) {
self?.messages.append(message)
self?.finishReceivingMessage()
}
}
else if media == "image" {
let storageRef = Storage.storage().reference(withPath: text)
storageRef.getData(maxSize: 1 * 1024 * 1024) { (data, error) -> Void in
if data != nil {
let image = UIImage(data: data!)
if let imageMessage = JSQMessage(senderId: id, senderDisplayName: name, date: self!.dateFormatter.date(from: timestamp), media: image as! JSQMessageMediaData) {
self?.messages.append(imageMessage)
print("image message")
self?.finishReceivingMessage()
}
}
}
}
}
})
}
If the media is an image, I want to create an image bubble, and when text a normal text bubble.
But I get no Image in the JSQMEssageViewController.
What went wrong? Can someone help me there?
I get the url in console:
https://firebasestorage.googleapis.com/v0/b/emmessenger-22.appspot.com/o/media%2FJzl0EUmjSvZZcpCd8Mdi8X9q87G2%2F03.06.2019%2021:26:39?alt=media&token=1c45feaf-0ed8-4f88-9c1f-13f4c6a8f3d4
And after that I get the following at console:
2019-06-03 21:28:21.092580+0200 EMMessenger[38502:554129] [] nw_proxy_resolver_create_parsed_array PAC evaluation error: NSURLErrorDomain: -1003
Thanks
Actually you don't need to call getData function for this case. As you're using tableView so I would recommend to use SDWebImage library to load the images on cells asynchronously. Once you have the imageURL just call below method
yourImageView.sd_setImage(with: URL(string: "https://firebasestorage.googleapis.com/v0/b/emmessenger-22.appspot.com/o/media%2FJzl0EUmjSvZZcpCd8Mdi8X9q87G2%2F03.06.2019%2021:26:39?alt=media&token=1c45feaf-0ed8-4f88-9c1f-13f4c6a8f3d4"), placeholderImage: UIImage(named: "placeholder.png"))
Rest the library will do for you.
When this code runs on a fresh app install, it works perfectly fine. However, when there is no data previously saved on the device, this function causes the app to crash.
I get the error Could not cast value of type __NSCFDictionary to NSData and it returns a thread zero error on the following line:
playlists = try! NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(playlistsData as! Data) as! [String:[Song]]
Here is my full function code below:
func getPlaylists() -> [String:[Song]] {
var playlists: [String:[Song]] = [:]
let playlistsData = defaults.object(forKey: "user_playlists")
if playlistsData != nil {
playlists = try! NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(playlistsData as! Data) as! [String:[Song]]
}
return playlists
}
To get the data safely change the method to
func getPlaylists() -> [String:[Song]] {
guard let playlistsData = defaults.data(forKey: "user_playlists"),
let playlists = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(playlistsData) as? [String:[Song]] else { return [:] }
return playlists
}
By the way the error occurs because you previously saved a dictionary to UserDefaults rather than Data.
You probably mean However, when there is data previously saved...
First time works as playlistsData is nil in the beginning
let playlistsData = defaults.object(forKey: "user_playlists")
>>>>>> hereis the crash >>>>> playlistsData as! Data
you should store the object in Data not as a Dictionary with
guard let data = try? NSKeyedArchiver.archivedData(withRootObject: somethingToSave, requiringSecureCoding: false) else { return }
defaults.set(data,forKey:"user_playlists")
I am using Swift to retrieve data from my Firebase database. When the user first logs in, I'd like to save the 'places' from my Firebase snapshot as a UserDefault.
static func getAllPlaces(){
databaseRef = Database.database().reference()
databaseRef.database.reference().child("places").observe(.childAdded) { (snapshot: DataSnapshot) in
if let value = snapshot.value as? NSDictionary {
let place = Place()
let id = value["id"] as? String ?? "ID not found"
let title = value["title"] as? String ?? "Title not found"
let type = value["type"] as? String ?? ""
place.id = id
place.title = title
place.type = type
DispatchQueue.global().async {
// Something here to append place data to UserDefaults?
places.append(place) // appends to NSObject for later use
}
}
}
}
The current code works fine - I just need to add something to get it stored so I can grab it later.
Bonus question: I am storing a good few hundred snapshots in the Firebase database. The reason I want to store them on the device is so that the user doesn't have to keep downloading the data. Is this a good idea? Would it take up a lot of memory?
Any help would be appreciated.
One way to save custom classes/data to UserDefaults is to encode them like this:
let encodedData: Data = NSKeyedArchiver.archivedData(withRootObject: place)
UserDefaults.standard.set(encodedData, forKey: "place")
UserDefaults.standard.synchronize()
Then you can decode it like this:
if UserDefaults.standard.object(forKey: "place") != nil{
let decodedData = UserDefaults.standard.object(forKey: "place") as! Data
let decodedPlace = NSKeyedUnarchiver.unarchiveObject(with: decodedData) as! Place
}
Updated for swift 4 and iOS 12:
do {
let encodedData: Data = try NSKeyedArchiver.archivedData(withRootObject: place, requiringSecureCoding: false)
UserDefaults.standard.set(encodedData, forKey: "place")
UserDefaults.standard.synchronize()
} catch {
//Handle Error
}
do {
if UserDefaults.standard.object(forKey: "place") != nil{
let decodedData = UserDefaults.standard.object(forKey: "place") as! Data
if let decodedPlace = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(decodedData) as? Place {
//Do Something with decodedPlace
}
}
}
catch {
//Handle Error
}
I have images saved in CloudKit as an asset. There are other attributes for each record as well. I can gather the record and use the other attributes, but I'm unable to use the asset in my ImageView. I'm new to Swift programming, therefore the error I receive does not make any sense.
let container = CKContainer.default()
let publicDB = container.publicCloudDatabase
let query1 = CKQuery(recordType: "movieArray", predicate: predicate2)
publicDB.perform(query1, inZoneWith: nil) {(results:[CKRecord]?, error:Error?) in
if error != nil {
DispatchQueue.main.async {
print("Cloud Query Error - Fetch Establishments: \(String(describing: error))")
}
return
}
for record in results! {
DispatchQueue.main.async {
let asset = record["myImageKey"] as? CKAsset
let data = NSData(contentsOf: (asset?.fileURL)!)
let image = UIImage(data: data! as Data)
print("Test")
self.detailImage.image = image
}
self.movieCast.text = record.object(forKey: "Actors") as? String
self.detailDescriptionLabel.text = record.object(forKey: "Description") as? String
let youLink = record.object(forKey: "youtubeTag") as! String
self.loadYoutube(videoID: youLink)
}
}
The error I get is on this line:
let data = NSData(contentsOf: (asset?.fileURL)!)
it says:
Thread 1: EXC_BREAKPOINT (code=1, subcode=0x1020527c4
I attempted to remove it from the main thread, but I receive the same error.
Maybe the problem is that the CKAsset record is nil, but you are forcing to have a fileURL value.
Try to obtain CloudKit image with this snippet
if let asset = record["myImageKey"] as? CKAsset,
let data = try? Data(contentsOf: (asset.fileURL)),
let image = UIImage(data: data)
{
self.detailImage.image = image
}