问题
I am trying to create something like Facebook NewsFeed where, I am using custom UICollectionViewCell to display data (Text/Image) from JSON. I have 2 different APIs. One for text and another for images(Every cell doesn't have image).
So, First of all I am getting all text values in to my cells from my textAPI and reloading myCollectionView. That works perfect.
Now for the Images, I am using ImageFetcher to fetch images,
func ImageFetcher(postId : NSNumber, completion : ((_ image: UIImage?) -> Void)!) {
var image = UIImage()
let urlString = "http://myImageAPI/Image/\(postId)"
let jsonUrlString = URL(string: urlString)
print(urlString)
URLSession.shared.dataTask(with: jsonUrlString!) { (data, response, error) in
do {
if let jsonData = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? [String:Any] {
if let images = jsonData["Image"] as? String {
if images == "" {
print("No Image")
} else {
let dataDecoded : Data? = Data(base64Encoded: images, options: .ignoreUnknownCharacters)
image = UIImage(data: dataDecoded!)!
completion(image)
}
}
}
else {
completion(nil)
}
} catch {
print(error.localizedDescription)
}
}.resume()
}
To display image in to Cell,
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// Displaying other elements with text
DispatchQueue.main.async {
self.ImageFetcher(postId: self.myArray[indexPath.item].id!, completion: { (image) -> Void in
customCell.mainImage.image = image
})
// Declared "indexPaths" var indexPaths = [IndexPath]()
// Added this lines
// let indexPath = IndexPath(item: indexPath.item, section: 0)
// self.indexPaths.append(indexPath)
}
return customCell
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.jsonParsing()
}
func jsonParsing() {
//Fetching text data from textAPI
//DispatchQueue.main.async {
// self.collectionView.reloadItems(at: self.indexPaths)
//}
}
Code works without any error but the issue is Images only appears when I click on them. (I can see empty/white imageView in a cell until I click it. As soon as I click on imageView the Image appears) I feel its something with DispatchQueue.main.async but, not sure.
I do not want to reload the whole collectionView after fetching the images. Just want to reload those cells which have Image. I found
collectionView.reloadItemsAtIndexPaths(myArrayOfIndexPaths)
on many solution but don't know how to make it work in this scenario. Can anyone please help me here? Any help will be much appreciated.
回答1:
Can you try this?
DispatchQueue.main.async {
customCell.mainImage.image = image
}
instead of
customCell.mainImage.image = image
===================== Updated ======================
I ended up helping Snehal not only image load problem but also a couple of other issues with collection view and image caches. I suggested him to use a third party library like SDWebImage but found out his api returns image as base64 string in the response. So I just go ahead and clean up his code and write the code that I think it's a good string point that might help him.
import UIKit
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
let imageView = UIImageView()
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.minimumInteritemSpacing = 10
layout.minimumLineSpacing = 10
layout.scrollDirection = .vertical
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(CustomCell.self, forCellWithReuseIdentifier: NSStringFromClass(CustomCell.self))
collectionView.backgroundColor = .clear
return collectionView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(collectionView)
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
collectionView.frame = view.bounds
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 50
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: NSStringFromClass(CustomCell.self), for: indexPath) as! CustomCell
cell.imageUrl = "http://myImageAPI/Image/\(postId)"
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.size.width - 2 * 20, height: 100)
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
}
}
class CustomCell: UICollectionViewCell {
let imageView = UIImageView()
var imageUrl: String? {
didSet {
if let imageUrl = imageUrl, let url = URL(string: imageUrl) {
dataTask = imageView.loadImage(url: url)
}
}
}
var dataTask: URLSessionDataTask?
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .white
imageView.backgroundColor = UIColor.lightGray
imageView.contentMode = .scaleAspectFit
contentView.addSubview(imageView)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func prepareForReuse() {
super.prepareForReuse()
dataTask?.cancel()
imageView.image = nil
}
override func layoutSubviews() {
super.layoutSubviews()
imageView.frame = bounds
}
}
extension UIImageView {
@discardableResult func loadImage(url: URL) -> URLSessionDataTask? {
if let image = ImageLoadManager.manager.cachedImages[url.absoluteString] {
self.image = image
return nil
}
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
do {
guard let data = data else {
return
}
if let jsonData = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String:Any] {
if let images = jsonData["PostImage"] as? String {// Pase the Base64 image string
if images == "" {
print("No Image")
} else {
if let dataDecoded = Data(base64Encoded: images, options: .ignoreUnknownCharacters), let decodedImage = UIImage(data: dataDecoded) {
DispatchQueue.main.async {
ImageLoadManager.manager.cachedImages[url.absoluteString] = decodedImage
self.image = decodedImage
}
}
}
}
}
else {
DispatchQueue.main.async {
self.image = nil
}
}
} catch {
print(error.localizedDescription)
}
}
task.resume()
return task
}
}
class ImageLoadManager {
static let manager = ImageLoadManager()
var cachedImages = [String: UIImage]()
}
回答2:
Better way to do this is like make a model that class that contains url of images and your text variables like class AnyName { var url: String? var desc: String?
init(urlValue: String?, descValue:String? ){
url = urlValue
desc = descValue
}
}
Now in your viewcontroller make array of above class name var items = AnyName Now by first api hitting you can set values for text in this array and passed to collectionview and load cells with text and thereafter you can change arrayvalues for url and reload collectionview easily
来源:https://stackoverflow.com/questions/45356157/reload-uicollectionviewcell-which-has-image