How to set it such that images are downloaded one by one and those that are downloaded are loaded first? Also, how to handle more image downloads as the user scrolls down while purging or clearing those on top?
Here's my code to download those images asynchronously from Firebase:
let databaseRef = FIRDatabase.database().reference()
databaseRef.child("palettes").queryOrdered(byChild: "top").queryEqual(toValue: "#000000").observeSingleEvent(of: .value, with: { (snapshot) in
if let snapDict = snapshot.value as? [String:AnyObject]{
for each in snapDict as [String:AnyObject]{
let URL = each.value["URL"] as! String
if let url = NSURL(string: URL) {
if let data = NSData(contentsOf: url as URL){
let image = UIImage(data: data as Data)
self.imageArray.append(image!)
self.collectionView?.reloadData()
}
}
}
}
})
And here's my code for populating collectionView:
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
let textLabel = cell.viewWithTag(2)
let ootdImage = cell.viewWithTag(4) as! UIImageView
ootdImage.image = imageArray[indexPath.row]
textLabel?.backgroundColor = averageColor
return cell
}
Edit: Right now, as my JSON tree only contains three entries, only three images are downloaded. But they are downloaded altogether and thus I figure it must be the reason why it takes a few seconds before all three images are downloaded and appear in the same instant.
func scrollViewDidScroll(scrollView: UIScrollView)
{
if scrollView.contentSize.height == scrollView.bounds.size.height + scrollView.bounds.origin.y {
//call web service here
}
}
Also add UIScrollViewDelegate in your class.
I found a solution to this:
Using alamofireImage,
let databaseRef = FIRDatabase.database().reference()
databaseRef.child("palettes").queryOrdered(byChild: "top").queryEqual(toValue: "#000000").observeSingleEvent(of: .value, with: { (snapshot) in
if let snapDict = snapshot.value as? [String:AnyObject]{
for each in snapDict as [String:AnyObject]{
let URL = each.value["URL"] as! String
self.URLArrayString.append(URL)
print(self.URLArrayString.count)
self.collectionView?.reloadData() //Reloads data after the number and all the URLs are fetched
}
}
})
after fetching all the URLs and total number of URLs, it will reload the cell immediately (images are not yet downloaded at this point, we let alamofireImage handle the image downloads one by one) which brings us to the next code:
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
let textLabel = cell.viewWithTag(2)
let ootdImage = cell.viewWithTag(4) as! UIImageView
// find out how to make it such that the data is gathered before being displayed.
//ootdImage.image = imageArray[indexPath.row]
let url = NSURL(string: URLArrayString[indexPath.row])
let placeholderImage = UIImage(named: "Rectangle")!
let filter = AspectScaledToFillSizeWithRoundedCornersFilter(
size: ootdImage.frame.size,
radius: 0
)
ootdImage.af_setImage(withURL: url as! URL, placeholderImage: placeholderImage, filter: filter, imageTransition: .crossDissolve(0.2)
)
textLabel?.backgroundColor = averageColor
return cell
}
Related
I've programmatically setup a collection view that should display an image from an API onto a cell. However, once the cells are displayed and data is called the cells remain empty.
Cells returning an empty image view
Current setup
override func viewDidLoad() {
super.viewDidLoad()
collectionView.register(ImageCell.self, forCellWithReuseIdentifier: imageCellId)
loadImageData(numberOfItems: numberOfItems)
}
func loadImageData(numberOfItems: Int) {
client.getImageData(items: numberOfItems, completion: { (error,data) in
if error != nil {
print("Error parsing image data")
} else {
self.per_page = data.perPage
self.total_results = data.totalResults
self.images = data.photos
for image in self.images {
self.userNameArray.append(image.photographer)
self.urlArray.append(image.url)
}
self.imageLinkArray = self.urlArray
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
})
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: imageCellId, for: indexPath) as! ImageCell
let imageString = String(imageLinkArray[indexPath.row])
let url = URL(string: imageString)
let data = try? Data(contentsOf: url!)
if let imageData = data {
let imageFromDatabase = UIImage(data: imageData)
cell.imageView.image = imageFromDatabase
}
return cell
}
Tried:
Made sure the URLs are coming back using a print statement for the url constant in cellForItemAt.
I've also tested out the cell layout by using a placeholder image.
Called collectionView.reloadData() in viewDidAppear().
Collection View Cells not appearing
Images not displayed in collection view cell
You should use Kingfisher as an image caching library
https://github.com/onevcat/Kingfisher
First, add the pod to your project:
pod 'Kingfisher'
Replace this:
let url = URL(string: imageString)
let data = try? Data(contentsOf: url!)
if let imageData = data {
let imageFromDatabase = UIImage(data: imageData)
cell.imageView.image = imageFromDatabase
}
with this
let url = URL(string: imageString)
imageView.kf.setImage(with: url)
The instance I've successfully called the images from array JSON returned object the UICollection is very slow to load especially if it has main images.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let childDict: NSDictionary = subCategoryData .object(at: indexPath.row) as! NSDictionary
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "listcollectionview", for: indexPath) as! subCategoryCollectionViewCell
subCategoryTable.register(UINib(nibName: "subCategoryCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "listcollectionview")
let subimages = childDict.object(forKey: "image") as! String!
let data = NSData(contentsOf: NSURL(string: subimages!)! as URL)
cell.backgroundImageView.image = UIImage(data: data! as Data)
cell.categoryName.text = (subCategoryMenuData [indexPath.row] as? String)
cell.categoryName?.textColor = UIColor.white
cell.categoryName?.backgroundColor = UIColor().HexToColor(hexString: GREYBLACK)
return cell;
}
I tried as well the dispatch.queue in didselectitemat when calling the segue but this didn't solve the problem
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let childDict: NSDictionary = subCategoryData .object(at: indexPath.row) as! NSDictionary
if (childDict.object(forKey: "children") as! NSArray).count > 0{
let sb = UIStoryboard(name: "Main", bundle: nil)
let initViewController: subCategory? = (sb.instantiateViewController(withIdentifier: "subCategory") as? subCategory)
initViewController?.subCategoryData = (childDict.object(forKey: "children") as! NSArray)
initViewController?.subName = childDict.object(forKey: "name") as! String!
initViewController?.subId = childDict.object(forKey: "path") as! String!
initViewController?.modalTransitionStyle = .flipHorizontal
self.navigationController?.pushViewController(initViewController!, animated: true)
}else{
categoryName = childDict .object(forKey: "name") as! String
categoryId = childDict .object(forKey: "path") as! String
DispatchQueue.main.async {
self.performSegue(withIdentifier: "productCategorySegue",sender: self)
}
}
}
It some times take 30 seconds to load
You load your image in cell for index you can use sdwebimage library install theough pods for lazy loading. It will definitely resolved your issue.
Oh I just found the solutions thanks to #abhishekkharwar post here
I converted the ObjC to Swift 3 to resolve the issue.
DispatchQueue.global(qos: .default).async(execute: {() -> Void in
var image = UIImage(contentsOfFile: frontPath)
DispatchQueue.main.async(execute: {() -> Void in
frontButton.setBackgroundImage(image, for: .normal)
})
})
Using AlamofireImage library:
let subimages = childDict.object(forKey: "image") as! String!
if let imageUrl = URL(string: subimages){
cell.backgroundImageView.af_setImage(withURL: imageUrl, placeholderImage: nil)
}
-----Swift 4-----
Step 1: Add this Extension : if you dont know about extension , check this : Swift extension example
extension UIImageView {
func downloadImage(from url:String){
if url != ""{
let urlRequest = URLRequest(url: URL(string:url)!)
let task = URLSession.shared.dataTask(with: urlRequest){(data,response,error) in
if error != nil{
return
}
DispatchQueue.main.async {
self.image = UIImage(data: data!)
}
}
task.resume()
}
}
}
Step 2 : Using UIImageViewExtension for Downloading Image :
In your 'cellForItemAt indexPath' method use this code :
cell.backgroundImageView.downloadImage(from: data)
Additional: check your image file size. if it is big in size thats obiously takes time to load . You can add this smooth image appearing animation before your image load for making it cool.
UIView.transition(with: cell.backgroundImageView,
duration: 0.5,
options: .transitionCrossDissolve,
animations: { cell.backgroundImageView.downloadImage(from: data) },
completion: nil)
When I scroll to the bottom of the UITableView the app is suppose to call a function ("CallAlamo(url: nextSearchURL)"), which just appends new content to array, then call tableView.reloadData(), and the tableview is then updated with the more content. However, the tableView freezes completely for about 2-3 seconds during this process. How can I get it to not freeze and work like most table views do in other apps where the new content is being loaded and the user is free to move the tableview.
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let lastElement = posts.count - 1
if indexPath.row == lastElement {
callAlamo(url: nextSearchURL) //appends new content to array
tableView.reloadData()
}
}
UPDATE
This is what callAlamo does:
func callAlamo(url : String){
Alamofire.request(url).responseJSON(completionHandler: {
response in
self.parseData(JSONData: response.data!)
})
}
func parseData(JSONData : Data){
do{
var readableJSON = try JSONSerialization.jsonObject(with: JSONData, options: .mutableContainers) as! JSONStandard
//print(readableJSON)
if let tracks = readableJSON["tracks"] as? JSONStandard{
nextSearchURL = tracks["next"] as! String
if let items = tracks["items"] as? [JSONStandard]{
//print(items) //Prints the JSON information from Spotify
for i in 0..<items.count{
let item = items[i]
let name = item["name"] as! String
let previewURL = item["preview_url"] as! String
if let album = item["album"] as? JSONStandard{
if let images = album["images"] as? [JSONStandard],let artist = album["artists"] as? [JSONStandard]{
let imageData = images[0] //this changes the quality of the album image (0,1,2)
let mainImageURL = URL(string: imageData["url"] as! String)
let mainImageData = NSData(contentsOf: mainImageURL!)
let mainImage = UIImage(data: mainImageData as! Data)
let artistNames = artist[0]
let artistName = artistNames["name"] as! String
posts.append(post.init(mainImage: mainImage, name: name, artistName: artistName, previewURL: previewURL))
self.tableView.reloadData()
}
}
}
}
}
} catch{
print(error)
}
}
UPDATE 2
Using #Anbu.Karthik choice 2:
Question 1: is "imageData" going to be my "mainImagedata"?
Question 2: I get an error in the Alamofire.request... saying "Extra argument 'method' in call" and when i delete it, i get an error that says "NSData? has no subscript members"
Very bad code design, you should pass the url to the cell and let it do the fetching and parsing, and you are doing this on the main queue. You can do this using(using another queue) DispatchQueue.global(qos: DispatchQoS.QoSClass.userInitiated).async. IDK if Alamofire calls your closure on the main queue, but it look like it does the request on it. And don't forget to get back on the main queue when you want do to UI using DispatchQueue.main.async
UPDATE: I hope that it was clear that reloadData(), kinda gives you an infinite loop, and you should call these outside the TableViewDataSource funcitons
UPDATE 2: I don't see it here, but you should use tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) and use in it let cell = tableView.dequeueReusableCell.....
I need some guidance in populating my collection view with images from Firebase. I've searched stack, google and youtube but couldn't find a fix for my problem. The captions (or "description/ requestDescription as I've named them) are displaying correctly, but the images related to each is not. There are 3 unique photos in the Firebase database but only two of them appear in the collection view (all 3 appear when I print the snapshotValue to the console though). Any guidance is greatly appreciated.
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return requestDescriptionsArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cellID = "CollectionViewCell"
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellID, for: indexPath) as! CollectionViewCell
//grab all images from database
ref = FIRDatabase.database().reference()
ref.child("Requests").queryOrdered(byChild: "replies").observe(.childAdded, with: { snapshot in
let snapshotValue = snapshot.value as? NSDictionary
print(snapshotValue!)
//get images
let photoURL = snapshotValue?["photo"] as! String
let url = URL(string: photoURL)
URLSession.shared.dataTask(with: url!, completionHandler: { (data, response, error) in
if error != nil {
print(error!)
return
}
DispatchQueue.main.async {
cell.photo.image = UIImage(data: data!)
}
}).resume()
})
cell.requestDescription.text = requestDescriptionsArray[indexPath.item]
return cell
}
func initForImages() {
ref = FIRDatabase.database().reference()
ref.child("Requests").queryOrdered(byChild: "replies").observe(.childAdded, with: { snapshot in
let snapshotValue = snapshot.value as? NSDictionary
print(snapshotValue!)
//get descriptons and add to array
let description = snapshotValue?["Description"] as! String
self.requestDescriptionsArray.insert(description, at: 0)
self.collection.reloadData()
})
}
I have a collection view in a view controller.. There is one problem which i can't figure out. The custom cell in a collection view is displaying one item from an array.
Cant figure out what is missing in the code.. I have used both the delegate and data source method..
Here is the code i am using..
viewDidLoad()
pathRef.observeSingleEventOfType(.ChildAdded, withBlock: { (snapshot) in
let post = CollectionStruct(key: snapshot.key, snapshot: snapshot.value as! Dictionary<String, AnyObject>)
self.userCollection.append(post)
let indexPath = NSIndexPath(forItem: self.userCollection.count-1, inSection: 0)
self.collectionView!.insertItemsAtIndexPaths([indexPath])
})
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return userCollection.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("CollectionCell", forIndexPath: indexPath) as! CollectionViewCell
let post = userCollection[indexPath.row]
if let imageUrl = post.category{
if imageUrl.hasPrefix("gs://"){
FIRStorage.storage().referenceForURL(imageUrl).dataWithMaxSize(INT64_MAX, completion: { (data, error) in
if let error = error {
print("Error Loading")
}
cell.userImg.image = UIImage.init(data: data!)
})
}else if let url = NSURL(string: imageUrl), data = NSData(contentsOfURL: url){
cell.userImg.image = UIImage.init(data: data)
}
}
return cell
}
I am trying to retrieve images stored in firebase database..
Use observeEventType instead of observeSingleEventOfType. More info in the docs.