How to make sequential firebase query in swift? - swift

Below is my data structure:
{
"posts": {
"xyz1": {
"author": "Jan",
"uid": "abc123",
},
"xyz2": {
"author": "Jenny",
"uid": "abc456",
},
}
"users": {
"abc123": {
"email": "Jan#gmail.com",
"profilePicURL": "https://firebasestorage.googleapis.com/v0/b/",
},
"abc456": {
"email": "Jenny#gmail.com",
"profilePicURL": "https://firebasestorage.googleapis.com/v0/c/",
},
}
}
I want to display the list of "posts" entries in a tableview.
let postRef = ref.child("posts")
postRef.observe(.childAdded, with: { (snapshot) in
let authorText = snapshot.value!.object(forKey: "author") as! String
let userIDText = snapshot.value!.object(forKey: "uid") as! String
}) { (error) in
print(error.localizedDescription)
}
How can i use the "uid" retrieved from the above query to make a sequential query to retrieve the "profilePicURL" using the "uid" value in the "users". End goal is to display profilePic stored besides the post in the tableview.
Thank you for any help rendered.

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "HomeCell", for: indexPath) as! HomeTableViewCell
cell.author.text = String(self.author[(indexPath as NSIndexPath).row])
let userIDText = String(self.userID[(indexPath as NSIndexPath).row])
ref.child("users").child(userIDText).observeSingleEvent(of: .value, with: { (snapshot) in
print("snaphot is \(snapshot)")
let imageLink = snapshot.value?["profileImageUrl"] as! String
self.storageRef = FIRStorage.storage().reference(forURL: imageLink)
cell.profilePic.loadImageUsingCacheWithUrlString(urlString: imageLink)
}) { (error) in
print(error.localizedDescription)
}
return cell
}
I use the following extension for UIImageView to load the image using the URL and it worked!!
let imageCache = NSCache<AnyObject, AnyObject>()
extension UIImageView {
func loadImageUsingCacheWithUrlString(urlString: String) {
self.image = nil
//check cache for image first
if let cachedImage = imageCache.object(forKey: urlString) as? UIImage {
self.image = cachedImage
return
}
//otherwise fire off a new download
let url = NSURL(string: urlString)
URLSession.shared.dataTask(with: url! as URL, completionHandler: { (data, response, error) in
//download hit an error so lets return out
if error != nil {
print(error)
return
}
DispatchQueue.main.async(execute: {
if let downloadedImage = UIImage(data: data!) {
imageCache.setObject(downloadedImage, forKey: urlString)
self.image = downloadedImage
}
})
}).resume()
}
}

Best idea is to store different Users into an Array of Users in which User is a Struct.
struct User {
var name: String = ""
var id: String = ""
}
Then in your ViewController you download the content from your Firebase and create Models of your User Struct.
let users: [User] = []
yourFireBaseQueryFunc() {
...
postRef.observe(.childAdded, with: { (snapshot) in
for item in snapshot {
let name = snapshot.value!.object(forKey: "author") as! String
let id = snapshot.value!.object(forKey: "uid") as! String
let user = User(name: name, id: id)
users.append(user)
}
Then for example in a tableView you take the indexPath and one Model out of your Model Array and call a function to get the Image Link from your Firebase:
cellForRowAtIndexPath... {
let user = users[indexPath.row]
let image = getImage(user.id)
let imgURL = NSURL(string: post.picture)
cell.yourImageView.sd_setImageWithURL(imgURL)
}
And then Query for the image:
func getImage(userID: String) -> String {
var imageLink: String = ""
let ref = firebase.child("users").child(userID)
ref.observeEventType(.Value, withBlock: { snapshot in
if snapshot.exists() {
imageLink = snapshot.value!.valueForKey("profilePicURL") as! String
}
})
return imageLink
}

Related

How to load image from Firebase into users avatar

I have a problem with loading images from firebase. I have two functions. One of them collect info about user, second one load users avatar image. Unfortunately images load after function creates new user. I know it will be problem with asynchronous of Firebase but I don't know how to set up DispatchQueue to work properly. Can you help me with that?
// function that load user image in user manager class
func loadUserImage(contactUserID: String, completion: #escaping (UIImage) -> Void) {
let userID = Auth.auth().currentUser!.uid
var userImageRef = self.storage.child("\(userID)/userImage.jpg")
var image = UIImage()
if contactUserID != "" {
userImageRef = self.storage.child("\(contactUserID)/userImage.jpg")
}
userImageRef.getData(maxSize: 5 * 1024 * 1024) { (data, error) in
if let error = error {
print("Error with retrieving data: \(error.localizedDescription)")
} else {
if data?.count != 0 {
image = UIImage(data: data!)!
} else {
image = UIImage(systemName: "person.circle.fill")!
}
completion(image)
}
}
}
// function that load user in contact manager class
func loadContactList(completion: #escaping ([User]) -> Void) {
let currentUserID = Auth.auth().currentUser!.uid
db.collection("contacts")
.document(currentUserID)
.collection("userContacts")
.addSnapshotListener { (querySnapshot, error) in
var contactList = [User]()
if let error = error {
print("Error with retrieving data from DB: \(error.localizedDescription)")
} else {
if let snapshotDocuments = querySnapshot?.documents {
for document in snapshotDocuments {
let data = document.data()
let uid = data["uid"] as! String
let name = data["name"] as! String
let email = data["email"] as! String
var contact = User(email: email, name: name, userID: uid)
DispatchQueue.global().sync {
self.userService.loadUserImage(contactUserID: uid) { (image) in
contact.photoURL = image
}
}
contactList.append(contact)
contactList.sort {
$0.name < $1.name
}
completion(contactList)
}
}
}
}
}
// Function implementation in viewController
func loadContactList() {
self.contactService.loadContactList { (contactArray) in
self.contactList = contactArray
self.tableView.reloadData()
}
}
What you can do is to store the image url in the firebase database and after that create this extension:
import UIKit
let imageCache: NSCache = NSCache<AnyObject, AnyObject>()
extension UIImageView {
func loadImageUsingCacheWithUrlString(urlString: String) {
self.image = nil
if let cachedImage = imageCache.object(forKey: urlString as AnyObject) as? UIImage {
self.image = cachedImage
return
}
let url = URL(string: urlString)
if let data = try? Data(contentsOf: url!) {
DispatchQueue.main.async(execute: {
if let downloadedImage = UIImage(data: data) {
imageCache.setObject(downloadedImage, forKey: urlString as AnyObject)
self.image = downloadedImage
}
})
}
}
}
And call:
if let url = data["imgUrl"] as? String {
self.myImageView.loadImageUsingCacheWithUrlString(urlString: url)
}
For that what you need to do is to create and initialize an UIImage object. If you are working with cell classes you need to create this object in the cell.

Swift: listen to url property then download photo

I run this code in viewDidLoad, but Profile.currentProfile does not have the photoUrl yet, so it is nil and this code never runs
private func getProfilePicture() {
if let photoURLString = Profile.currentProfile?.photoUrl {
if let photoURL = URL(string: photoURLString) {
if let photoData = try? Data(contentsOf: photoURL) {
self.profilePhotoView.image = UIImage(data: photoData)
self.letsGoButton.isEnabled = true
}
}
} else {
self.profilePhotoView.image = UIImage(named: "default-profile-icon")
}
}
How can I wait until the photoUrl is not nil, then run this code? Thanks
Rik
(edit) this is how profile is set. This is called before the viewController is instantiated.
func copyProfileFieldsFromFB(completionHandler: #escaping ((Error?) -> Void)) {
guard AccessToken.current != nil else { return }
let request = GraphRequest(graphPath: "me",
parameters: ["fields": "email,first_name,last_name,gender, picture.width(480).height(480)"])
request.start(completionHandler: { (_, result, error) in
if let data = result as? [String: Any] {
if let firstName = data["first_name"] {
Profile.currentProfile?.firstName = firstName as? String
}
if let lastName = data["last_name"] {
Profile.currentProfile?.lastName = lastName as? String
}
if let email = data["email"] {
Profile.currentProfile?.email = email as? String
}
if let picture = data["picture"] as? [String: Any] {
if let imageData = picture["data"] as? [String: Any] {
if let url = imageData["url"] as? String {
Profile.currentProfile?.photoUrl = url
}
}
}
}
completionHandler(error)
})
}
Normally, you'll want to use completion handlers to keep track of asynchronous activities. So in your viewDidLoad() you could call something like
Profile.currentProfile?.getPhotoURL { urlString in
if let photoURL = URL(string: photoURLString) {
if let photoData = try? Data(contentsOf: photoURL) {
self.profilePhotoView.image = UIImage(data: photoData)
self.letsGoButton.isEnabled = true
}
}
}
And on your Profile class it would look something like this:
func getPhotoURL(completion: #escaping (urlString) -> Void) {
// get urlString
completion(urlString)
}
You can add private var profileUrl and use didSet observing with it:
... //e.g. your controller
private var profileUrl: URL? {
didSet {
if let url = profileUrl {
getProfilePicture(from: url)// update your func
}
}
}

CollectionViewCell loading with nil value after reloadData func

I made a function to fetch data for an empty array that I'm using for a collectionView. I'm pulling the information from two different child nodes. The first being the "users" tree and the second being the "profile_images", using the UID from users to find the corresponding images. The cell populates when the view loads. My issue is that when the cell populates, I'm getting a nil value for one of the values.
I tried to add the array to the collectionViewCell instead of the view controller. I've also been reading the developer notes on prefetching data but it makes it seems like it's used for cells that have yet to be loaded.
var matches = [MatchData]()
// function to retrieve firebase data
private func populateInbox() {
if let uid = Auth.auth().currentUser?.uid {
// Supply Matches for users first
let match = MatchData()
Database.database().reference().child("users").observe(.childAdded) { (snapshot) in
let matichUID = snapshot.key
if matichUID != uid {
Database.database().reference().child("profile_images").child(matichUID).observeSingleEvent(of: .value, with: { (data) in
if let imageDict = data.value as? [String: AnyObject] {
match.matchImage = imageDict["imageOne"] as? String
print(match.matchImage)
}
})
if let dictionary = snapshot.value as? [String: AnyObject] {
print(uid, dictionary)
match.matchName = dictionary["firstName"] as? String
self.matches.append(match)
}
}
DispatchQueue.main.async {
self.matchList.reloadData()
print(self.matches.count)
}
}
}
}
// function to convert image url into UIImage
private func icon(_ imageURL: String, imageView: UIImageView) {
let url = URL(string: imageURL)
var image: UIImage?
var imageData:Data?
if url == nil {
print("Code failed here...")
imageView.image = #imageLiteral(resourceName: "ic_person_outline_white_2x")
} else {
URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error != nil {
print("error")
DispatchQueue.main.async {
imageView.image = UIImage(imageLiteralResourceName: "ic_person_outline_white_2x")
}
} else {
DispatchQueue.main.async {
imageData = data
image = UIImage(data: imageData!)
imageView.image = image!
}
}
}.resume()
}
}
// Data model
class MatchData: NSObject {
var matchImage: String?
var matchName: String?
}
// additional details
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "InboxCell", for: indexPath) as! InboxCell
let matchInfo = matches[indexPath.row]
cell.userLabel.text = matchInfo.matchName
icon(matchInfo.matchImage ?? "", imageView: cell.userImage)
//icon always returns nil value but Userlabel returns name value
return cell
}
The expected result is to have a cell that displays images along with the name of the user the image belongs too. The actual results is the name of the users profile and a nil value for the image.
It looks like you append match to your matchlist before your observeSingleEventOf callback completes. Match updates when the image is received, but has already been added.
if let dictionary = snapshot.value as? [String: AnyObject] {
match.matchName = dictionary["firstName"] as? String
}
if matchUID != uid {
Database.database().reference().child("profile_images").child(matichUID).observeSingleEvent(of: .value, with: { (data) in
if let imageDict = data.value as? [String: AnyObject] {
match.matchImage = imageDict["imageOne"] as? String
}
self.matches.append(match)
DispatchQueue.main.async {
self.matchList.reloadData()
}
})
} else {
self.matches.append(match)
DispatchQueue.main.async {
self.matchList.reloadData()
}
}

download images from url to image array and show in table view swift [duplicate]

This question already has answers here:
Loading/Downloading image from URL on Swift
(39 answers)
Closed 3 years ago.
I am facing trouble getting image from my json url.
this is my json:
"bank_details": [
{
"id": 1,
"logo": "http://mortgagemarket.ae/webApi/public/mortgage_bank_icons/noorebank.png",
"name": abc company
}
]
my swift code to parse the image is this:
import UIKit
class BanksViewController: UIViewController, UITableViewDelegate,UITableViewDataSource {
final let BANKS_URL = "http://www.mortgagemarket.ae/webApi/api/manage_interest_rates"
#IBOutlet weak var tableView: UITableView!
var bankicon = [String]()
var bankname = [String]()
var bankid = [Int]()
let stringid: String = ""
override func viewDidLoad() {
super.viewDidLoad()
self.displayFromDb()
tableView.dataSource = self
tableView.delegate = self
}
func displayFromDb()
{
let tokensp = UserDefaults.standard.string(forKey: "tokenKey")
let url = NSURL(string: BANKS_URL+"?token="+tokensp!)
print(url)
URLSession.shared.dataTask(with: (url as?URL)!, completionHandler: {(data,response,error) ->
Void in
if let jsonObj = try? JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? NSDictionary
{
print(jsonObj.value(forKey: "bank_details")!)
if let messageArray = jsonObj.value(forKey: "bank_details") as? NSArray
{
print(jsonObj.value(forKey: "bank_details")!)
for message in messageArray
{
if let messageDict = message as? NSDictionary
{
if let data = data {
if let bankname = messageDict.value(forKey: "bank_name")
{
self.bankname.append(bankname as! String)
print(bankname)
}
if let banklogo = messageDict.value(forKey: "logo")
{
self.bankicon.append(banklogo as! String)
print(banklogo)
}
if let bankid = messageDict.value(forKey: "id")
{
self.bankid.append(bankid as! Int)
print(bankid)
}
OperationQueue.main.addOperation({
self.tableView.reloadData()
})
}
}
}
}
}
}).resume()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return (bankname.count)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! BanksTableViewCell
cell.bankicon.image = bankicon[indexPath.row] as? UIImage
cell.bankname.text = bankname[indexPath.row]
return (cell)
}
}
now When I run this code it is showing blank table cells. I dont know how to get image from url and display the images in table view cell. Please someone help me.
this is my whole code to get the all the json data into table view cell. Please someone help me
imageicon[indexPath.row] gives a urlStringand not the instance ofUIImage. You need to fetch the image from server using this urlString.
Use URLSession to fetch the image from server like,
if let url = URL(string: imageicon[indexPath.row]) {
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let data = data {
DispatchQueue.main.async {
cell.imageicon.image = UIImage(data: data)
}
}
}.resume()
}
Your models should be like this:
/// Your response models
struct BankDetails: Codable {
let bank_details: [ImageUrl]
}
struct ImageUrl: Codable {
let logo: String
}
And then in your cell:
class MyCell: UITableViewCell {
/// create dataTask for cancel in prepareForReuse function
private var dataTask: URLSessionDataTask?
/// Like this
override public func prepareForReuse() {
super.prepareForReuse()
dataTask?.cancel()
}
func populate(with model: YourModel) {
/// You should set url in indexPath of your logo array([ImageUrl])
let url = model.url /// It's sample url for explain this is an url of your current index model
if let imageUrl = url {
downloaded(from: imageUrl)
}
}
func downloaded(from url: URL, contentMode mode: UIView.ContentMode = .scaleAspectFit) {
contentMode = mode
dataTask = URLSession.shared.dataTask(with: url) { data, response, error in
guard
let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
let mimeType = response?.mimeType, mimeType.hasPrefix("image"),
let data = data, error == nil,
let image = UIImage(data: data)
else { return }
DispatchQueue.main.async() {
self.yourImageView.image = image
}
}
dataTask?.resume()
}
}
In your Controller's tableView cellForRowAt function:
let model = models[indexPath.row]
cell.populate(with: model)
return cell
You can use the above models, and create displayFromDb like this:
func displayFromDb() {
let tokensp = UserDefaults.standard.string(forKey: "tokenKey")
let url = NSURL(string: BANKS_URL+"?token="+tokensp!)
if let myUrl = url {
URLSession.shared.dataTask(with: myUrl) { (data, response , error) in
guard let data = data else { return }
do {
let decoder = JSONDecoder()
let data = try decoder.decode(BankDetails.self, from: data)
print("my logo array is: \(data.bank_details)")
// TODO: - So you get urls
} catch let err {
print("Err", err)
}
}.resume()
}
}

How to show image view in table view from json

To parse json i have following function
func single_news(userid: Int) {
var request = URLRequest(url: URL(string: news_url)!)
request.httpMethod = "POST"
//Pass your parameter here
let postString = "userid=\(userid)"
request.httpBody = postString.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
print("error=(error)")
return
}
let json: Any?
do
{
json = try JSONSerialization.jsonObject(with: data, options: [])
print("abcnews")
//here is your JSON
print(json)
let jsonValue : NSDictionary = json as! NSDictionary
self.results = jsonValue.object(forKey: "data") as! [[String:String]]
self.DiscoveryNewsTableView.delegate = self
self.DiscoveryNewsTableView.dataSource = self
self.DiscoveryNewsTableView.reloadData()
// let _ = getData.shared.getDataForTableView(dict: json)
}
catch
{
return
}
guard let server_response = json as? NSDictionary else
{
return
}
}
task.resume()
}
To get data the class is created
class getData: NSObject {
var descriptionn : String = ""
var image : String = ""
// static let shared = getData()
func getDataForTableView(results: [[String:String]], index : Int){
var productArray = [String:String]()
productArray = results[index]
descriptionn = productArray["description"]!
image = productArray["images"]!
}
}
To display data in table view
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "discoveryNewscell") as! DiscoveryNewsTableViewCell
// if results.count > 0{
classObject.getDataForTableView(results: results, index: indexPath.row)
cell.sneakerImageView.image=filteredsneakernews[indexPath.row].image
print("abc image"+classObject.image)
cell.newsTitle.text = classObject.descriptionn
// }
return cell
}
How to display the image .Image(classObject.image) in string format how to display image view on table view ?you can download the code from this link .https://drive.google.com/file/d/1bVQsuSQINSa6YRwZe2QwEjPpU_m7S3b8/view?usp=sharing
You're wanting to display an image but you only have the URL to that image and not the image itself so you'll need to download it, then display it. I have a class I use a lot that allows you to simply call one line to download AND cache the image so you'll be able to do something like this:
classObject.getDataForTableView(results: results, index: indexPath.row)
let image_url = filteredsneakernews[indexPath.row].image
cell.sneakerImageView.loadImageUsingCacheWithUrlString(urlString: image_url!)
To do this, you'll have to copy the class below and inside your cell class, you’ll want to change the imageView type from a standard UIImageView to a CustomImageView for example:
let imageView: CustomImageView!
//
import UIKit
let imageCache = NSCache<NSString, UIImage>()
class CustomImageView: UIImageView {
var imageUrlString: String?
func loadImageUsingCacheWithUrlString(urlString: String) {
imageUrlString = urlString
if let cachedImage = imageCache.object(forKey: urlString as NSString) {
self.image = cachedImage
return
}
self.image = nil
let url = URL(string: urlString)
URLSession.shared.dataTask(with: url!, completionHandler: { (data, response, error) in
if error != nil { return }
DispatchQueue.main.async {
if let downloadedImage = UIImage(data: data!) {
if self.imageUrlString == urlString {
if self.imageUrlString != "" {
self.image = downloadedImage
} else {
self.image = nil
}
}
imageCache.setObject(downloadedImage, forKey: urlString as NSString)
}
}
}).resume()
}
}