I have a UIImageView and I want to append there images coming from a remote URL
Up to now it works fine with only one image.
I just set cell.related.image to the new image and I return always 1 to the collectionView
How can I switch to appending an array of images?
Is there a way to do the appending in a function?
Do I need always to do the appending inside collectionView ?
I want to do it inside an event listener in the future
var img_uls = ["url1" , "url2", "url3"]
func append_image(_ path: String, _ cell: RelatedCollectionViewCell) {
let url = URL(string: path)
DispatchQueue.global().async {
let data = try? Data(contentsOf: url!) //make sure your image in this url does exist, otherwise unwrap in a if let check / try-catch
DispatchQueue.main.async {
cell.related.image = UIImage(data: data!)
}
}
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return img_uls.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "RelatedCollectionViewCell", for: indexPath) as! RelatedCollectionViewCell
append_image(img_uls[indexPath.row], cell);
return cell
}
Just have the array of String which contains Url of your image and pass it as in numberOfItems Method like this
var img_uls = ["url1" , "url2", "url3"]
//array of strings containing urls
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return img_uls.count
}
Now you have to iterate your array img_uls, So that you can fetch Url from array and fetch the images and set in collectionView cell. So, do it like this
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "RelatedCollectionViewCell", for: indexPath) as! RelatedCollectionViewCell
append_image(img_uls[indexPath.row], cell);
// indexPath.row starts from (0) and goes to (img_uls.count - 1)
return cell
}
Now you want to append the image into images array
var images = NSMutableData()
func append_image(_ path: String, _ cell: RelatedCollectionViewCell) {
let url = URL(string: path)
DispatchQueue.global().async {
let data = try? Data(contentsOf: url!) //make sure your image in this url does exist, otherwise unwrap in a if let check / try-catch
DispatchQueue.main.async {
images.appendData(data)
cell.related.image = UIImage(data: data!)
}
}
}
Instead of using your own append method, and handling that append and all, you can use Kingfisher or Haenke. They are lightweight and moreover manages caching. And hence your scrolling will be fast.
Create a imageUrlArray variable of String which will hold the remote image URLs, then return the count of the array from your numberOfItemsInSection method. In collectionView:cellForItemAt function get the Url to display from the array using imageArray[indexPath.row] and pass the Url to the append_image(_ path: String, _ cell: RelatedCollectionViewCell) function. Better use third party image loading library like KingFisher or Nuke to better handle Asynchronous image downloading and caching.
class Example1: UIViewController, UICollectionViewDataSource {
var imageArray: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
}
func updateImages(_ images: [String]) {
self.imageArray = images
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return imageArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "RelatedCollectionViewCell", for: indexPath) as! RelatedCollectionViewCell
//append_image(imageArray[indexPath.row], cell)
//Using Kingfisher
let url = imageArray[indexPath.row]
cell.imageView.kf.setImage(with: url, placeholder: nil, options: [.transition(.fade(1))])
return cell
}
}
Related
I've been working with Swift and iOS for several months now. I want to download a new image when it comes to CollectionView's penultimate item. I'm downloading, but the reloadData function is constantly redrawing and collectionview is top. How can I prevent it from going to the top?
private func getRandomPhoto() {
let url = "https://source.unsplash.com/random/400x800"
do{
var a = try Data(contentsOf: URL(string: url)!)
images.append(UIImage(data: a)!)
} catch {
print("err")
}
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
and here is my controller view codes
extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return images.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! ImageCollectionViewCell
cell.imageView.image = images[indexPath.row]
return cell
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
for cell in collectionView.visibleCells {
let indexPathh = collectionView.indexPath(for: cell)
if indexPathh?.row == images.count-1 {
let url = "https://source.unsplash.com/random/400x800"
getRandomPhoto()
}
}
}
}
I made a small video for the problem. I hope it's clear.
https://github.com/Egemenclr/StajCase/blob/master/Örnek/problem.gif
Don't reload the whole table, instead insert rows for newly downloaded images
insertItems(at indexPaths: [IndexPath])
suggestion: Don't download the same image only again and again. Download initially and cache it with NSCache. Try to fetch from cache when you need the image again.
I have an array (4 items) of type Venue each contains an array (2 items) of imgURLs String and I am getting an ERROR: Index out of range at the following line of my collectionView cellForItemAt. I am trying to display the images for each Venue in a collectionView within the tableview cell.
let urlString = filteredVenueArray[collectionView.tag].imgURLs[collectionView.tag]
cellForItemAt function:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "VenueListingCellImage", for: indexPath) as! VenueListingCellImage
let urlString = filteredVenueArray[collectionView.tag].imgURLs[collectionView.tag]
let imgURL = URL(string: urlString)
cell.configureCell(url: imgURL!)
return cell
}
EDIT: numberOfItemsInSection function
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return filteredVenueArray[collectionView.tag].imgURLs.count
}
Change this line
let urlString = filteredVenueArray[collectionView.tag].imgURLs[collectionView.tag]
To
let urlString = filteredVenueArray[collectionView.tag].imgURLs[indexPath.item]
In your code replace:
let urlString = filteredVenueArray[collectionView.tag].imgURLs[collectionView.tag]
with:
let urlString = filteredVenueArray[collectionView.tag].imgURLs[indexPath.item]
CollectionView cellForItemAt indexPath iterates your array through indexPath, so to access each item you should use indexPath.item
Here is the problem, you should use,
let urlString = filteredVenueArray[collectionView.tag].imgURLs[indexPath.item]
Trying to load data in collection view with this method
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if urls.count == 0 {
return UICollectionViewCell()
}
let url = urls[indexPath.item]
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier,
for: indexPath) as! ImageCollectionViewCell
storageRef.reference(withPath: url).getData(maxSize: 15 * 1024 * 1024, completion: { data, error in
if let data = data {
cell.imageView.image = UIImage(data: data)
}
})
return cell
}
The thing is - at first i can see both cells without data, then completion get called, an i'm getting a data, but the first image in both cells.
How can i do it right? And how can i get metadata in the same time too?
Screenshot:
UPD:
I wrote an array
var datas = ["first", "second", "third", "fourth"]
An changed cell code to
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier,
for: indexPath) as! ImageCollectionViewCell
cell.nameLabel.text = datas[indexPath.item]
return cell
}
And got this:
Still can't see what's wrong. My overrided methods:
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 2
}
func collectionView(_ collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int {
return datas.count / 2
}
SOLVED. Should return numberOfSections = 1
I think the problem is that you're downloading the information in the cellForItemAt indexPath: function and you should download the images in another function like this
//Variable to store the UIImages
var images = [UIImage]()
//function to download the images, run it in the viewdidload like self.getImages()
func getImages(){
for url in self.urls{
storageRef.reference(withPath: url).getData(maxSize: 15 * 1024 * 1024, completion: { data, error in
if let data = data {
self.images.append(UIImage(data: data))
}
})
}
self.YourCollectionView.reloadData()
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if urls.count == 0 {
return UICollectionViewCell()
}
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier,
for: indexPath) as! ImageCollectionViewCell
cell.imageView.image = self.images[indexPath.row]
return cell
}
Should return 1 in numberOfSection func
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 2
}
In my project CollectionView in TableViewCell isn't display and I added
cell.collectionView.reloadData()
in my code. After I added, CollectionView displayed in TableViewCell, but
ScrollView isn't smooth. How to solve this problem. If someone have any experience or ideas help me. Thanks.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "homecell") as! HomeCategoryRowCell
let mainThemeList = mainHomeThemeTable[(indexPath as NSIndexPath).row]
DispatchQueue.main.async {
cell.categoryTitle.text = mainThemeList.main_name
cell.collectionView.reloadData()
}
return cell
}
CollectionView in my project,
extension HomeCategoryRowCell : UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
debugPrint("assetsTable count \(assetsTable.count)")
return assetsTable.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "videoCell", for: indexPath) as! HomeVideoCell
let list = assetsTable[(indexPath as NSIndexPath).row]
let url2 = NSURL(string:list.poster_image_url)
let data2 = NSData(contentsOf:url2! as URL)
// debugPrint(list.poster_image_url)
DispatchQueue.main.async(){
cell.movieTitle.text = list.name
cell.imageView.image = UIImage(data: data2! as Data)
}
return cell
}
}
Download Image Url Data in collectionView: UICollectionView, cellForItemAt indexPath: IndexPath delegate - ASYNCHRONOUSLY
Create a Class
First cache the image Data with respect to key Image URL
Why do we need to cache the data?
Because - While scrolling we don't need to download Image Every Time so Cache the already downloaded Image Data and return the cache data
I created a class name: AsyncImageLoader then I created a function which is a completion handler which returns the data after completion of downloading Image
class AsyncImageLoader {
static let cache = NSCache<NSString, NSData>()
class func image(for url: URL, completionHandler: #escaping(_ image: UIImage?) -> ()) {
DispatchQueue.global(qos: DispatchQoS.QoSClass.background).async {
if let data = self.cache.object(forKey: url.absoluteString as NSString) {
DispatchQueue.main.async { completionHandler(UIImage(data: data as Data)) }
return
}
guard let data = NSData(contentsOf: url) else {
DispatchQueue.main.async { completionHandler(nil) }
return
}
self.cache.setObject(data, forKey: url.absoluteString as NSString)
DispatchQueue.main.async { completionHandler(UIImage(data: data as Data)) }
}
}
}
How to use AsyncImageLoader Class?
See below How I used in collectionView: UICollectionView, cellForItemAt indexPath: IndexPath
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let yourCell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCollectionCell", for: indexPath) as! CustomCollectionCell
yourCell.url = URL(string: "\(item_ImageUrl)")
return yourCell
}
CustomCollectionCell
class CustomCollectionCell: UICollectionViewCell {
#IBOutlet weak var cellImageDisplayView: UIImageView!
var url: URL? {
didSet {
if let url = url {
cellImageDisplayView.image = nil
AsyncImageLoader.image(for: url, completionHandler: { (image) in
DispatchQueue.main.async {
if let img = image {
self.cellImageDisplayView.image = img
}else{
let dummyImage = UIImage(named: "img_u")
self.cellImageDisplayView.image = dummyImage
}
}
})
}else{
let dummyImage = UIImage(named: "img_u")
self.cellImageDisplayView.image = dummyImage
}
}
}
}
cell.layer.shouldRasterize = true
cell.layer.rasterizationScale = UIScreen.main.scale
After I added above code , my project become more smooth. I'll try the best and back with the best solution. Thanks
This is because you are directly getting data from url and then image from data in main thread. You should use image downloading library to download the image from url
Here is the good third party to download image from url asynchronously.
https://github.com/rs/SDWebImage
Try with this I am 100% sure it will solve your problem.
Thanks
How to hide the cell collectionview in section if json data is nil. im loading collectionview in a tableview cell.my code is belowim showing array of images in collection view i have used third party for horizontalautomatic scroll as third party for collection view
func setCollectionData(_collectionData: [Any]) {
self.collectionData = _collectionData as NSArray
collectionViewObj.setContentOffset(CGPoint(x:0,y:0), animated: true)
for i in 0 ..< collectionData.count {
self.strings = StringClass()
self.strings.vehicleImage = (collectionData[i] as AnyObject).value(forKey: "img_name") as? String
self.strings.vehicleTitle=(collectionData[i] as AnyObject).value(forKey: "p_name") as? String
self.modelArray.append(self.strings)
}
collectionViewObj.reloadData()
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
if self.collectionData.count >= 4 {
return 4
}
else
{
return collectionData.count
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ProductCollectionViewCustomCell", for: indexPath)as! ProductCollectionViewCustomCell
strings = modelArray[indexPath.row]
cell.ProductTitleLabel.text = strings.vehicleTitle
let URLBaseString = "http://vehiclebuzzzz.com/"
let url = URL(string:URLBaseString .appending(strings.vehicleImage!))
let dataurl = try? Data(contentsOf: url!)
cell.productImageViewObj.image=UIImage(data: dataurl!)
return cell
}
func collectionView(_ collectionView: UICollectionView,layout collectionViewLayout: UICollectionViewLayout,sizeForItemAt indexPath: IndexPath) -> CGSize
override this method and return zero size CGSize.zero if the json data is nil else return actual size of cell
OR
remove nil data from array and call collectionView.reloadData()
Update the collectionData array with the new data & then perform collectionViewObj.reloadData()
-If you does not want show collection view. just hide the collection view until you get the data.
-When you data make it visible