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()
}
}
Related
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()
}
}
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()
}
}
I am making a swift app where i am downloading data from API. which gives a JSON.And from there i am putting image url in an imageArray and movieTiteUrl in movieTitleArray. but when i am showing them to collection view they are showing data but that data is not related. To download images i am using AlamofireImage Below code will help you to understand my problem better.
inside ViewDidLoad
var imageUrlArray = [String]()
var imageArray = [UIImage]()
var movieTitleArray = [String]()
UICollectionViewDelegate
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "movieCell", for: indexPath) as? MovieCell else { return UICollectionViewCell() }
cell.movieName.image = imageArray[indexPath.row]
cell.movieNameLbl.text = movieTitleArray[indexPath.row]
return cell
}
An extension which download data and download images
func downloadImages(handler: #escaping (_ status: Bool)-> ()){
imageArray = []
movieTitleArray = []
for url in imageUrlArray{
Alamofire.request(url).responseImage(completionHandler: { (response) in
guard let image = response.result.value else { return }
self.imageArray.append(image)
if self.imageArray.count == self.imageUrlArray.count {
handler(true)
}
})
}
}
func retriveData(handler : #escaping (_ status: Bool) -> ()){
print(getPopularMovies(pageNumber: 1))
Alamofire.request(getPopularMovies(pageNumber: 1)).responseJSON { (response) in
guard let json = response.result.value as? Dictionary<String, AnyObject> else { return }
let dataDictArray = json["results"] as! [Dictionary<String, AnyObject>]
for data in dataDictArray {
guard let imageUrl = data["poster_path"] as? String else { return }
guard let name = data["original_title"] as? String else { return }
let updatedImageUrl = getFullImageUrl(imageUrl: imageUrl)
self.imageUrlArray.append(updatedImageUrl)
self.movieTitleArray.append(name)
}
handler(true)
}
}
func updateDataToCollectionView(){
retriveData{(finished) in
if finished{
self.downloadImages(handler: { (finishedDownloadingImage) in
if finishedDownloadingImage{
self.movieCollectionView.reloadData()
}
})
}
}
}
I have found two observation ( problems ).
As you are using Async request to download the images, it is not guaranteed that you will get the response in the requested order. for example, if you request movie1Image,movie2Image....it is not guarntee that first you will receive movie1Image and the movie2Image in the same order. So definitely it is not reliable. use Dictionary to solve this. [String:Data] string -> ImageUrl, Data -> ImageData
Why did you wait till all images get downloaded? any specific scenario or any business requirement. Ideally, for a greater User Experience and for interaction, it is not recommended to wait till all the images get downloaded.
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
}
In a collection view I have an array of events and each cell has a background image view of the event image which is saved as a PFFile. That works fine and good until I added this code. The user has a property "profilePicture" which I want to display in the cell as well. Here is my code which is inside of the block which gets the event image.
let eventCreator : PFUser = event?.objectForKey("user") as! PFUser
let creatorImage : PFFile = eventCreator.objectForKey("profilePicture") as! PFFile
creatorImage.getDataInBackgroundWithBlock({ (data, error) -> Void in
cell.creatorImageView.image = UIImage(data: data!)
})
Here is the full method which gets the event and all it's properties (like which I said, worked perfectly fine before I added the above code. Now it throws an "fatal error: unexpectedly found nil while unwrapping an Optional value" error. Any help?
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
//sets up cell
let cell : EventCell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as! EventCell
//adds attend action
cell.attendButton.addTarget(self, action: "buttonTapped:", forControlEvents: UIControlEvents.TouchUpInside)
//queries parse for events
let event = events?[indexPath.row]
event?.eventImage.getDataInBackgroundWithBlock({ (data, error) -> Void in
if let data = data, image = UIImage(data: data) {
if cell.isFlipped == false {
cell.eventBackgroundImage.image = image
cell.eventTitleLabel.text = event?.eventTitle
//gets profile picture of events creator
let eventCreator : PFUser = event?.objectForKey("user") as! PFUser
let creatorImage : PFFile = eventCreator.objectForKey("profilePicture") as! PFFile
creatorImage.getDataInBackgroundWithBlock({ (data, error) -> Void in
cell.creatorImageView.image = UIImage(data: data!)
})
//sets correct category for cell image
if event?.category == "" {
cell.categoryImageView.image = nil
}
if event?.category == "The Arts" {
cell.categoryImageView.image = UIImage(named: "University")
}
if event?.category == "The Outdoors" {
cell.categoryImageView.image = UIImage(named: "Landscape")
}
//TODO finish categories
}
else if cell.isFlipped == true {
cell.eventDescriptionLabel.text = event?.eventDescription
}
}
})
Forcefully casting a nil-valued optional variable to a non-optional one will lead to a runtime error. Why are you using as! operator ? You should probably use the as? operator instead and check for any nil values to make sure the cast was successful before doing anything.
[Edit]
Try something like this:
if let eventCreator = event?.objectForKey("user") as? PFUser {
if let creatorImage = eventCreator.objectForKey("profilePicture") as? PFFile {
creatorImage.getDataInBackgroundWithBlock({ (data, error) -> Void in
cell.creatorImageView.image = UIImage(data: data!)
})
}
}