Function does not return array. Download photos from Firebase Storage matching Firebase Database file names [duplicate] - swift

This question already has answers here:
Returning data from async call in Swift function
(13 answers)
Closed 4 years ago.
I have images stored in FireBase Storage, and matching file name data in FireBase Database, and I want to get those photos and display them (note, there is still some code I need to write because I am not getting EVERY photo from storage. Just those that are returned from a query of the database)
Here is the git repo
This code in DBHandler works, as I can see the print of the image file names
func photoListForLocation() -> [String]{
let file_name:String = String()
var photos = [file_name]
ref.observeSingleEvent(of: .value) { (snapshot) in
if let snapshot = snapshot.children.allObjects as? [DataSnapshot]{
for snap in snapshot {
if let data = snap.value as? [String:Any]{
let imageName:String = data["image_name"]! as! String
photos.append(imageName)
print("photos.append - \(imageName)")
}//if let data
}//for
}//snapshot
}//ref.observeSingleEvent
return photos
}//photoListForLocation
BUT the "return photos" never happens.. So the following in my ViewController does nothing..
let dbHandler:DBHandler = DBHandler()
var fileList = [String]()
fileList = dbHandler.photoListForLocation()
fileList.forEach {fileName in
print("\(fileName)")
}
Of course, if there is a better or simpler way of accomplishing my goal, I'm all ears.
for Mr. Tomato... (see comments)
import Foundation
import FirebaseDatabase
import GoogleMaps
class DBHandler {
var ref:DatabaseReference! = Database.database().reference().child("locations")
var imageCount:Int = 0
func addLocation(coordinate:CLLocationCoordinate2D, rating: Double, imageName: String?){
let location = ["latitude": coordinate.latitude,
"longitude": coordinate.longitude,
"rating": rating,
"image_name": imageName!,
"postDate": ServerValue.timestamp()
] as [String : Any]
self.ref.childByAutoId().setValue(location)
}//end setLocation
func getImageListForLocation(lattitude:Double, longitude:Double) -> [String]{
var images = [String]()
self.ref.observeSingleEvent(of: .value) { (snapshot) in
if let snapshot = snapshot.children.allObjects as? [DataSnapshot]{
for snap in snapshot {
if let data = snap.value as? [String:Any]{
let thisLattitude = data["latitude"]
let thisLongitude = data["longitude"]
guard let imageName = data["image_name"] else {return}
if lattitude == thisLattitude as! Double && longitude == thisLongitude as! Double {
images.append(imageName as! String)
}//if
}//if
}//for
}//if
}//ref
self.imageCount = images.count
return images //DOES NOT RETURN IMAGES!! (FILE NAMES)
}//getImageListForLocation
}//DBHandler

In order to get the photos and display them, you need to store the Storage URL of the photo location in your database for later use. Here are a couple of functions I created for a project that does this.
This application has a list of Angels that it saves and retrieves. Angels have names, numbers, emails, and images. I store a local array of these angels in a datasource I've defined in PageDataSource.sharedInstance(). The boolean crudIsAvailable is to make sure there is a connection. On verifying that CRUD operations are available I being scrubbing the list of angelsToSave:
func saveAngels(_ completion: #escaping(_ error: Error?) -> Void) {
if PageDataSource.sharedInstance.crudIsAvailable == true {
let angelsRef = ref.child("angels")
let myAngelsRef = angelsRef.child(id)
for item in PageDataSource.sharedInstance.angelsToSave {
let angel = PageDataSource.sharedInstance.angels[item]
let angelNameRef = myAngelsRef.child(angel.name!)
var angelToSave = getAngel(angel)
var jpegRepresentation : UIImage? = nil
if let photo = angel.photo {
jpegRepresentation = photo
} else {
jpegRepresentation = UIImage(named: "Anonymous-Seal")
}
if let photoData = UIImageJPEGRepresentation(jpegRepresentation!, 1.0) {
storePhoto(photoData, angel.name!, completion: { (url, err) in
if err != nil {
print(err?.localizedDescription)
angelToSave["photo"] = nil
myAngelsRef.updateChildValues(angelToSave, withCompletionBlock: { (error, ref) in
if error != nil {
completion(error!)
} else {
completion(nil)
}
})
} else {
angelToSave["photo"] = url?.absoluteString
angelNameRef.updateChildValues(angelToSave)
completion(nil)
}
})
}
}
} else {
completion(NSError(domain: "Unavailable", code: 0, userInfo: nil))
}
}
The important part of saveAngels() is if let photoData..... storePhoto() and here is the storePhoto() function.
func storePhoto(_ photo: Data, _ name: String, completion: #escaping (_ result: URL?, _ error: NSError?) -> Void) {
let storageRef = Storage.storage().reference().child(name)
storageRef.putData(photo, metadata: nil) { (storageMetaData, err) in
if err != nil {
completion(nil, NSError(domain: (err?.localizedDescription)!, code: 0, userInfo: nil))
} else {
completion(storageMetaData?.downloadURL(), nil)
}
}
}
The function storePhoto() returns the value of the URL through a completion handler and the saveAngels() function takes that information and uses it to store the data to the realtime database for future use.
For a better understanding here is my Angel object:
class Angel: NSObject {
var name: String?
var email: [String]?
var phone: [String]?
var photo: UIImage?
var filepath: String?
}
And here is how I download the photo:
First, I retrieve the list of photo URLs to an array of "angels" in a datasource singleton on VC load then I load the images as they appear in a collectionView like this.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellID", for: indexPath) as! AngelCollectionViewCell
cell.imageView?.contentMode = .scaleAspectFill
cell.activityIndicator.hidesWhenStopped = true
cell.activityIndicator.startAnimating()
if let pic = PageDataSource.sharedInstance.angels[indexPath.row].photo {
cell.imageView?.image = pic
cell.activityIndicator.stopAnimating()
cell.setNeedsLayout()
} else if let imgURL = PageDataSource.sharedInstance.angels[indexPath.row].filepath {
Storage.storage().reference(forURL: imgURL).getData(maxSize: INT64_MAX, completion: { (data, error) in
guard error == nil else {
print("error downloading: \(error!)")
return
}
// render
let img = UIImage.init(data: data!)
// store to datasource
PageDataSource.sharedInstance.angels[indexPath.row].photo = img
// display img
if cell == collectionView.cellForItem(at: indexPath) {
DispatchQueue.main.async {
cell.imageView?.image = img
cell.activityIndicator.stopAnimating()
cell.setNeedsLayout()
}
}
})
} else {
// TODO: TODO: Change Image to a proper placeholder
cell.imageView.image = UIImage(contentsOfFile: "Angels#2x.png")
cell.Label.text = PageDataSource.sharedInstance.angels[indexPath.row].name!
cell.activityIndicator.stopAnimating()
}
return cell
}
The important code really starts with Storage.storage I hope this helps!!

Related

Using Firebase to populate TableviewCell with Collectionview inside

I can populate my TableviewCell with images and upload them to Firebase successfully but not to my CollectionView inside the cell. However, I can upload them to a CollectionView on another ViewController as long as it's not inside a TableViewCell. I experimented with reloading the data but nothing appears and isn't required by the one on the other ViewController. I'm trying to get the "showcaseImages" to appear in the collectionView.
This is what I have to save the data in my structure:
func save ( userInfo: UserInfo, completion: #escaping (Result < Bool, NSError>) -> Void) {
var showcaseImagesDict = [String : String]()
userInfo.showcaseImages.forEach { showcaseImagesDict[UUID().uuidString] = $0 }
userReference.addDocument(data: ["profileImage": userInfo.profileImage, "profileName": userInfo.profileName, "showcaseImages": showcaseImagesDict
]) { (error) in
if let unwrappedError = error {
completion(.failure(unwrappedError as NSError))
}else {
completion(.success(true))
}
}
}
This is what I have to Listen:
func listen (completion : #escaping ([UserInfo]) -> Void) {
userReference.addSnapshotListener { (snapshot, error) in
guard let unwrappedSnapshot = snapshot else {return}
let documents = unwrappedSnapshot.documents
var usersInfo = [UserInfo]()
for document in documents {
let documentData = document.data()
guard
let profileImage = documentData["profileImage"] as? String,
let profileName = documentData["profileName"] as? String,
let showcaseImagesDict = documentData["showcaseImages"] as? [String : String]
else {continue}
let showcaseImages = showcaseImagesDict.map {$0.value}
let userInfo = UserInfo(profileImage: profileImage, profileName: profileName, showcaseImages: showcaseImages)
usersInfo.append(userInfo)
}
completion(usersInfo)
}
}
Heres what I have to populate the TableviewCell:
func populate(with user: UserInfo){
profileName.text = user.profileName
imageCache2?.getImage(named: user.profileImage,completion: { [weak self](image) in
self?.userImage.image = image
//self?.user = user
})
}
This is to populate CollectionViewCell:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CVCell", for: indexPath)
let collectionViewCell1 = cell as? CollectionViewCell
collectionViewCell1?.imageCache4 = imageCache3
let imagePath = showcaseImages[indexPath.item]
collectionViewCell1?.populate(with: imagePath)
return cell
}

Fetch data from multiple node in Firebase in Swift

I built my app to have news feed like Facebook. My problem is that I don't know how to fetch child images in Post and show it in a collectionView. Please show me how to do it. Appreciate any help.
Here is the db structure:
Posts
d7j3bWMluvZ6VH4tctQ7B63dU4u1:
20181112101928:
avatar: "https://platform-lookaside.fbsbx.com/platform/p..."
content: "Funny image"
images:
-LR4vaEIggkGekc-5ZME:
"https://firebasestorage.googleapis.com/v0/b/hon..."
-LR4vaENC-IsePibQYxY:
"https://firebasestorage.googleapis.com/v0/b/hon..."
name: "Thành Trung"
time: 1541992768776.3628
type: "Funny"
Here is my code:
func getDataFromPostFirebase() {
let getPostData = databaseReference.child("Posts")
getPostData.observe(.childAdded) { (snapshot) in
getPostData.child(snapshot.key).observe(.childAdded, with: { (snapshot1) in
getPostData.child(snapshot.key).child(snapshot1.key).observe(.value, with: { (snapshot2) in
self.arrayImageUrl = [String]()
if let dict = snapshot2.value as? [String : Any] {
guard let avatar = dict["avatar"] as? String else {return}
guard let content = dict["content"] as? String else {return}
guard let name = dict["name"] as? String else {return}
guard let time = dict["time"] as? Double else {return}
guard let type = dict["type"] as? String else {return}
if let images = dict["images"] as? [String : String] {
for image in images.values {
self.arrayImageUrl.append(image)
}
let newPost = Post(avatarString: avatar, contentString: content, nameString: name, timeDouble: time, typeString: type)
self.arrayPost.append(newPost)
DispatchQueue.main.async {
self.feedTableView.reloadData()
}
} else {
let newPost = Post(avatarString: avatar, contentString: content, nameString: name, timeDouble: time, typeString: type)
self.arrayPost.append(newPost)
DispatchQueue.main.async {
self.feedTableView.reloadData()
}
}
}
})
})
}
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return arrayImageUrl.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionViewCell", for: indexPath) as! TrangChu_CollectionViewCell
cell.imgContent.layer.cornerRadius = CGFloat(8)
cell.imgContent.clipsToBounds = true
cell.imgContent.layer.borderWidth = 2
cell.imgContent.layer.borderColor = #colorLiteral(red: 0.4756349325, green: 0.4756467342, blue: 0.4756404161, alpha: 1)
let url = URL(string: arrayImageUrl[indexPath.row])
cell.imgContent.sd_setImage(with: url, completed: nil)
return cell
}
Model object
import Foundation
class Post {
var avatar : String
var content : String
var images : [String]?
var name : String
var time : Double
var type : String
init(avatarString : String, contentString : String, nameString : String, timeDouble : Double, typeString : String) {
avatar = avatarString
content = contentString
// images = imagesString
name = nameString
time = timeDouble
type = typeString
}
}
As what I've said your db is not well structured. I suggest you re structure it like this.
Posts
d7j3bWMluvZ6VH4tctQ7B63dU4u1:
avatar: "https://platform-lookaside.fbsbx.com/platform/p..."
content: "Funny image"
images:
-LR4vaEIggkGekc-5ZME: "https://firebasestorage.googleapis.com/v0/b/hon..."
-LR4vaENC-IsePibQYxY: "https://firebasestorage.googleapis.com/v0/b/hon..."
name: "Thành Trung"
time: 1541992768776.3628
type: "Funny"
timestamp: 1540276959924
I removed the timestamp node and transferred it along the children node. Now you can fetch the posts with this.
ref.child("Posts").observe(.childAdded) { (snapshot) in
var post = Post()
let val = snapshot.value as! [String: Any]
post.name = val["name"] as? String
self.ref.child("Posts").child(snapshot.key).child("images").observeSingleEvent(of: .value, with: { (snap) in
post.imagesString = [String]()
for image in snap.children.allObjects as! [DataSnapshot] {
post.imagesString?.append(image.value as! String)
print("images \(image.value)")
}
list.append(post)
print("post \(post)")
})
If you want to order the posts you can achieve it using queryOrderedByChild("timestamp")
Add this to access your images:
guard let images = dict["images"] as? [[String: Any]] { return }
let imagesString: [String] = []
for imageDict in images {
for key in imageDict.keys {
if let imageName = imageDict[key] as? String else {
// here you access your image as you want
imagesString.append(imageName)
}
}
}
Then when creating the post object you use imagesString that we created:
let newPost = Post(avatarString: avatar, contentString: content, imagesString: imagesString, nameString: name, timeDouble: time, typeString: type)
You can fetch the images values using this
ref.child("Posts").observe(.childAdded) { (snapshot) in
self.ref.child("Posts").child(snapshot.key).observe(.childAdded, with: { (snapshot1) in
self.ref.child("Posts").child(snapshot.key).child(snapshot1.key).child("images").observe(.childAdded, with: { (snap) in
let post = new Post()
for rest in snap.children.allObjects as! [DataSnapshot] {
//append images
post.imagesString.append(rest.value)
}
post.avatarString = snapshot1.value["avatar"] as? String
...
})
})
I suggest you change the structure of your db because its nested. Refer here

Firestore - Creating a copy of a collection

So I have a collection called "Drafts" which contains multiple documents each with an auto ID. Each document contains the fields "name" and "details". Each document is displayed in a tableViewCell under "nameLabel" and "detailsLabel". What I would like to do is when the user clicks on a button at the top of the screen of the First viewController, a copy of the collection "Drafts" is created and pasted under a new collection name called "Messages". This collection is then referencing the Second viewControllers tableViewCells just like on the First ViewController only this time its being referenced under the collection "Messages". Having done some research I have a vague inclination that the answer uses cloud functions to essentially create a copy of the collection and paste it with a new collection name. However being relatively new to coding and firebase, I have no idea how to do this and don't know if this is the correct solution. Please may someone help, any help is greatly appreciated!! Thanks!
First ViewController
func loadDrafts() {
let userRef = db.collection("Users").document(user!)
let draftsRef = userRef.collection("Drafts")
exercisesRef.getDocuments { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
if let snapshot = querySnapshot {
for document in snapshot.documents {
let data = document.data()
let name = data["name"] as? String ?? ""
let details = data["details"] as? String ?? ""
let newDrafts = DraftMessages(name: name, details: details)
self.array.append(newDrafts)
}
self.tableView.reloadData()
}
}
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! DraftsCell
cell.nameLabel.text = array[indexPath.row].name
cell.detailsLabel.text = array[indexPath.row].details
return cell
}
#IBAction func goButton(_ sender: UIButton) {
\\ Add code here to create copy of previous collection "Drafts" and paste in new collection "Messages"
}
Second ViewController
func loadData() {
let userRef = db.collection("Users").document(user!)
userRef.collection("Messages").getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
let data = document.data()
let name = data["name"] as? String ?? ""
let details = data["details"] as? String ?? ""
let newMessages = Messages(name: name, details: details)
self.array.append(newMessages)
}
self.tableView.reloadData()
}
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! MessagesCell
cell.nameLabel.text = array[indexPath.row].name
cell.detailsLabel.text = array[indexPath.row].details
return cell
}
Here is my working solution. Many thanks to Franks for the help!
#IBAction func goButton(_ sender: UIButton) {
let userRef = db.collection("Users").document(user!)
let draftsRef = userRef.collection("Drafts")
draftsRef.getDocuments { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
if let snapshot = querySnapshot {
for document in snapshot.documents {
let data = document.data()
let batch = self.db.batch()
let docset = querySnapshot
let messagesRef = userRef.collection("Messages").document()
docset?.documents.forEach {_ in batch.setData(data, forDocument: messagesRef)}
batch.commit(completion: { (error) in
if let error = error {
print("\(error)")
} else {
print("success")
}
})
}
}
}
}
}
Edit for Vaibhav Jhaveri:
This function (hopefully) both duplicates the fetched documents data and the data inside of that documents subcollection. (I have not tested this though)
func duplicate() {
let userRef = db.collection("Users").document(userID)
let batch = self.db.batch()
let draftsRef = userRef.collection("Drafts")
draftsRef.getDocuments { (snapshot, err) in
if let err = err {
print(err.localizedDescription)
return
}
guard let snapshot = snapshot else { return }
snapshot.documents.forEach({ (document) in
let data = document.data()
let messageID = UUID().uuidString
let messagesRef = userRef.collection("Messages").document(messageID)
batch.setData(data, forDocument: messagesRef, merge: true)
let yourSubCollectionRef = draftsRef.document(document.documentID).collection("yourSubCollection")
yourSubCollectionRef.getDocuments(completion: { (subSnapshot, subErr) in
if let subErr = subErr {
print(subErr.localizedDescription)
return
}
guard let subSnapshot = subSnapshot else { return }
subSnapshot.documents.forEach({ (subDocument) in
let subData = subDocument.data()
let subDocID = UUID().uuidString
let yourNewSubCollectionRef = userRef.collection("Messages").document(messageID).collection("yourSubCollection").document(subDocID)
batch.setData(subData, forDocument: yourNewSubCollectionRef, merge: true)
})
})
})
batch.commit()
}
}

How to prevent tableView from crashing with asynchronous methods?

I am making a tableView with pictures inside it. The pictures are the profile pictures from people which can join, leave and make there own room. In that room, the tableView is displayed.
To save data, I want to store every profile picture in the documents directory when people are joining the room. Now that is working, but the tableView reloads to often even when I am calling it twice. Because it reloads to much, it crashes because the array is out of index.
The crash happens to be on the picture array, not the username array. This is my code (a bit much):
private var playersRefHandle: FIRDatabaseHandle?
var channelRef: FIRDatabaseReference?
var players = [String]()
var playerImages = [UIImage]()
var playerUIDs = [String]()
var playersImageVersion = [String]()
var channel: Channel? {
didSet {
title = channel?.name
}
}
override func viewDidLoad() {
super.viewDidLoad()
playersView.delegate = self
playersView.dataSource = self
let storage = FIRStorage.storage()
let storageRef = storage.reference(forURL: "gs://X-f5beb.appspot.com")
channelRef?.observeSingleEvent(of: .value, with: { (snapshot) in
if let snapDict = snapshot.value as? [String:AnyObject]{
for each in snapDict{
let UIDs = each.value["userID"] as? String
let pictureVersion = each.value["PictureVersion"] as? String
if let allUIDS = UIDs{
if let allPictureVersions = pictureVersion{
self.playerUIDs.append(UIDs!)
self.playersImageVersion.append(allPictureVersions)
let userNames = each.value["username"] as? String
if let users = userNames{
self.players.append(users)
}
if self.checkDataExist(dataToCheck: "\(UIDs!)" + "Image.png") == false || self.isCurrentImageVersionStored(pathToImage: "\(UIDs!)" + "ImageVersion.txt", playersVersionImage: "\(allPictureVersions)") == false
{
print("image needs to be downloaded online")
let profilePicRef = storageRef.child((allUIDS)+"/profile_picture.png")
profilePicRef.data(withMaxSize: 1 * 500 * 500) { data, error in
if let error = error {
}
if (data != nil)
{
let image = UIImage(data: data!)
let convertImage = UIImagePNGRepresentation(image!)
let pathUIDImage = self.getDocumentsDirectory().appendingPathComponent(allUIDS + "Image.png")
try? convertImage!.write(to: pathUIDImage)
let playersUIDImageVersion = allPictureVersions
var pathUIDImageVersion = self.getDocumentsDirectory().appendingPathComponent(allUIDS + "ImageVersion.txt")
try? playersUIDImageVersion.write(to: pathUIDImageVersion, atomically: true, encoding: .utf8)
self.playerImages.append(UIImage (data: data!)!)
}
}
}
else
{
self.playerImages.append(self.retrieveImageFromDocuments(playersUID: UIDs!))
}
}
}
}
}
self.playersView.reloadData()
self.observePlayers()
})
}
deinit {
if let refHandle = playersRefHandle {
channelRef?.removeObserver(withHandle: refHandle)
}
}
override func willMove(toParentViewController parent: UIViewController?)
{
if parent == nil
{
print("back")
}
}
func getDocumentsDirectory() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let documentsDirectory = paths[0]
return documentsDirectory
}
func checkDataExist(dataToCheck: String) -> Bool
{
let documentsURL = try! FileManager().url(for: .documentDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: true)
let file = documentsURL.appendingPathComponent(dataToCheck)
let fileExists = FileManager().fileExists(atPath: file.path)
if fileExists == true
{
return true
}
else
{
return false
}
}
func isCurrentImageVersionStored(pathToImage: String, playersVersionImage: String) -> Bool
{
if let dir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
let path = dir.appendingPathComponent(pathToImage)
do {
let currentStoredImage = try String(contentsOf: path, encoding: String.Encoding.utf8)
let currentStoredImageInt = Int(currentStoredImage)
let playersVersionImageInt = Int(playersVersionImage)
if currentStoredImageInt == playersVersionImageInt
{
return true
}
else
{
return false
}
}
catch {/* error handling here */}
return false
}
return false
}
func retrieveImageFromDocuments(playersUID: String) -> UIImage
{
print("image is available offline")
let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
let dirPath = paths.first
let imageURL = URL(fileURLWithPath: dirPath!).appendingPathComponent("\(playersUID)" + "Image.png")
let profileImageForUser = UIImage(contentsOfFile: imageURL.path)
return profileImageForUser!
}
func observePlayers()
{
playersRefHandle = channelRef?.child("username").observe(.childChanged, with: { (snapshot) -> Void in
print("added player")
})
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return players.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print("configuring one of multiple cells")
print(playerImages.count)
print(players.count)
var cell = tableView.dequeueReusableCell(withIdentifier: "playersCell") as! PlayersCellInMultiplayer
cell.playersUsername?.text = players[indexPath.row] as String
cell.playersImage?.image = playerImages[indexPath.row] as UIImage
return cell
}
This is my print of a random channel with a few users in it:
image is available offline
image needs to be downloaded online
configuring one of multiple cells
1
2
configuring one of multiple cells
1
2
Why does this happen? Is this of asynchronous methods? What is the best way to fix it?

How do I save and show a Image with core data - swift 3

I am doing a project in Swift 3 - xcode 8, and I am trying to use core data to save and show some images in a data base table "users".
This image is the user photo in his profile.
Now I've managed to save strings and showing them from core data but I am having problems in working this out with images.
This is what I have so far:
Adding USERS into core data
func addUser() {
let app = UIApplication.shared.delegate as! AppDelegate
let context = app.persistentContainer.viewContext
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Users")
request.returnsObjectsAsFaults = false
let newUser = NSEntityDescription.insertNewObject(forEntityName: "Users", into: context)
if (firstName.text == "" && lastName.text == "" && contact.text == "" && email.text == "") { //if we have a user profile delete it
deleteUser()
} else { // add a new user profile
newUser.setValue(firstName.text, forKey: "firstName")
newUser.setValue(lastName.text, forKey: "lastName")
newUser.setValue(contact.text, forKey: "contact")
newUser.setValue(email.text, forKey: "email")
//newUser.setValue(imageView.image, forKey: "photo")
//let imgUrl = UIImagePickerControllerReferenceURL as! NSURL
let img = UIImage(named: "f.png")
let imgData = UIImageJPEGRepresentation(img!, 1)
newUser.setValue(imgData, forKey: "photo")
print ("Data added in Users")
}
do {
try context.save()
//print("saved!!!")
Alert.show(title: "Success", message: "Profile Saved", vc: self)
} catch {
// print ("Error")
Alert.show(title: "Error", message: "Profile not Saved", vc: self)
}
}
Showing Users from core data
func showUser() {
let app = UIApplication.shared.delegate as! AppDelegate
let context = app.persistentContainer.viewContext
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Users")
request.returnsObjectsAsFaults = false
do {
let results = try context.fetch(request)
if results.count > 0 {
print("Profile: Data Found:")
for result in results as! [NSManagedObject] {
if let firstNameinData = result.value(forKey: "firstName") as? String{
firstName.text = firstNameinData
print(firstNameinData)
}
if let lastNameinData = result.value(forKey: "lastName") as? String{
lastName.text = lastNameinData
print(lastNameinData)
}
if let contactinData = result.value(forKey: "contact") as? String{
contact.text = contactinData
print(contactinData)
}
if let emailinData = result.value(forKey: "email") as? String{
email.text = emailinData
print(emailinData)
}
if let photoinData = result.value(forKey: "photo") as? UIImage{
imageView.image = photoinData
}
}
} else { // if there is not a user profile
firstName.text = ""
lastName.text = ""
contact.text = ""
email.text = ""
print("Profile : No data found")
}
//print("Loaded!!!")
} catch {
print ("Error Loading")
}
}
I cannot show the image I have saved.
Do you have any tips?
EDIT: Xcode gives me this message "Connection to assetsd was interrupted or assetsd died"
The property photo of Users is (NS)Data, as you do there, converting the
UIImage into NSData.
let img = UIImage(named: "f.png")
let imgData = UIImageJPEGRepresentation(img!, 1)
newUser.setValue(imgData, forKey: "photo")
While when you retrieve the info, you are doing like photo was a UIImage object:
if let photoinData = result.value(forKey: "photo") as? UIImage{
imageView.image = photoinData
}
This is not logical according to previous lines. It should be something like that:
if let imageData = result.value(forKey: "photo") as? NSData {
if let image = UIImage(data:imageData) as? UIImage {
imageView.image = image
}
}
Note: I don't speak Swift, so the proposed code may not compile, but you should get the idea of what's wrong and what's need to be done.
Larme has it almost spot on, but instead of this:
if let image = UIImage(data:imageData) as? UIImage
do this:
if let image = UIImage(data: imageData as Data)
Hope i helps you. work fine for me
var results :[Any] = []
let image = UIImage(named: "image.png")
//this is the line that appears to be wrong
let imageData = UIImagePNGRepresentation(image!) as NSData?
guard let appDelegate =
UIApplication.shared.delegate as? AppDelegate else {
return
}
// 1
let managedContext =
appDelegate.persistentContainer.viewContext
// 2
let entity =
NSEntityDescription.entity(forEntityName: "Image",
in: managedContext)!
let person = NSManagedObject(entity: entity,
insertInto: managedContext)
// 3
person.setValue(imageData, forKeyPath: "name")
// 4
do {
try managedContext.save()
results.append(person)
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
Hope this will help you. First I too had a great confusing of storing the image in core Data.
This is used to save the image in coreData
First create Nsmanaged Object Class
class Item: NSManagedObject {
}
Declare the image as NSData
import CoreData
extension Item {
#NSManaged var image: NSData?
#NSManaged var name: String?
#NSManaged var email: String?
}
Now go the View Controller you want to save the image .
class newViewController: UIViewController ,UIImagePickerControllerDelegate,UINavigationControllerDelegate{
var item : Item? = nil
var imagePicker = UIImagePickerController()
var PassImages = UIImage()
#IBOutlet var name: UITextField!
#IBOutlet var email: UITextField!
#IBOutlet var photoclick: UIButton!
var context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
#IBAction func clickaction(_ sender: Any) {
if UIImagePickerController.isSourceTypeAvailable(UIImagePickerController.SourceType.photoLibrary){
print("Button capture")
let picker = UIImagePickerController()
picker.allowsEditing = true
picker.sourceType = .photoLibrary
picker.delegate = self //Don't forget this line!
self.present(picker, animated: true, completion: nil)
}
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
var selectedImage: UIImage?
if let editedImage = info[.editedImage] as? UIImage {
selectedImage = editedImage
self.image.image = selectedImage!
picker.dismiss(animated: true, completion: nil)
} else if let originalImage = info[.originalImage] as? UIImage {
selectedImage = originalImage
self.image.image = selectedImage!
picker.dismiss(animated: true, completion: nil)
}
}
func imagePickerControllerDidCancel(picker: UIImagePickerController!) {
self.dismiss(animated: true, completion: nil)
}
#IBOutlet var image: UIImageView!=nil
#IBAction func submit(_ sender: Any) {
if name.text != "" && email.text != ""
{
let entityDescription = NSEntityDescription.entity(forEntityName: "Table", in: context)
let item = Item(entity: entityDescription!, insertInto: context)
item.name = name.text
item.email = email.text
item.image = image.image!.pngData()! as NSData
do {
try context.save()
print("saved this moc")
} catch {
return
}
let UserDetailsVc = self.storyboard?.instantiateViewController(withIdentifier: "ViewController") as! ViewController
self.navigationController?.pushViewController(UserDetailsVc, animated: true)
}
else
{
print("mail check")
let alertController1 = UIAlertController (title: "Fill Email id", message: "Enter valid email", preferredStyle: UIAlertController.Style.alert)
alertController1.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
present(alertController1, animated: true, completion: nil)
}
}
override func viewDidLoad() {
super.viewDidLoad()
if item != nil {
name.text = item?.name
email.text = item?.email
image.image = UIImage(data: (item?.image)! as Data)
}
}
This controller is used to fetch everything
class ViewController: UIViewController ,UITableViewDataSource,UITableViewDelegate,NSFetchedResultsControllerDelegate{
var userarray: [Table] = []
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return userarray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath) as! TableViewCell
let name = userarray[indexPath.row]
cell.username.text = name.name
cell.showImage?.image = UIImage(data: (name.image)!)
return cell
}
#IBOutlet var table: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
fetchData()
}
override func viewWillAppear(_ animated: Bool) {
fetchData()
}
func fetchData(){
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
do {
userarray = try context.fetch(Table.fetchRequest())
print(userarray,"user")
}catch{
print("error")
}
}
}