I am experiencing very choppy scrolling in my collection view. There are only 10 cells. It is because of my method of retrieving the images which is to take the URL and turn it to UIImage data.
Images variable is just an array of image URLs.
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return images.count
}
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "mediaCells", for: indexPath) as! MediaCell
let url = URL(string: images[indexPath.row] as! String)
let data = try? Data(contentsOf: url!)
cell.mediaImage.image = UIImage(data: data!)
return cell
}
A solution I found was to do this first for all images. The problem with this approach is that it will have to wait for all images to download the data and append to the array before we display the collectionView. This can take quite a few seconds 10-15 seconds before we can render the collection. Too slow!
override func viewDidLoad() {
super.viewDidLoad()
Alamofire.request(URL(string: "myURL")!,
method: .get)
.responseJSON(completionHandler: {(response) -> Void in
if let value = response.result.value{
let json = JSON(value).arrayValue
for item in json{
let url = URL(string: item["image"].string!)
let data = try? Data(contentsOf: url!)
self.images.append(data)
}
self.collectionView.reloadData()
}
})
}
Used SDWebImage from cocoapods to do Async image fetching.
https://cocoapods.org/?q=SDWebImage
cell.mediaImage.sd_setImage(with: URL(string: images[indexPath.row] as! String))
Some will shout about the use of globals, but this is the way I did previously:
Declare a global array of UIImage
var images: [UIImage] = []
When image is downloaded, append it to that array
for item in json{
let url = URL(string: item["image"].string!)
let data = try? Data(contentsOf: url!)
let image = UIImage(data: data!)
images.append(image)
NotificationCenter.default.post(Notification.Name(rawValue: "gotImage"), nil)
}
Follow the Notification in your VC
override function viewDidLoad() {
NotificationCenter.default.addObserver(self, selector: #selector(collectionView.reloadData()), name: NSNotification.Name(rawValue: "gotImage"), object: nil)
super.viewDidLoad()
}
Do not assign an image if not available in your cell for collectionView
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
.....
if let image = images[indexPath].row {
cell.mediaImage.image = image
}
....
}
PS: If all the downloading and loading action happens in the same VC, just declare images inside your VC class.
Seems like you need to do the lazy loading of images in your collection view based on the visible cells only. I have implemented the same approach like this.
1) First steps you need to create a class named it as PendingOperations.swift
class PendingOperations {
lazy var downloadsInProgress = [IndexPath: Operation]()
lazy var downloadQueue: OperationQueue = {
var queue = OperationQueue()
queue.name = "downloadQueue"
queue.maxConcurrentOperationCount = 1
return queue
}()
}
2) Seconds steps create a class named it as ImageDownloader where you can make your network call to download the image.
class ImageDownloader: Operation {
let imageId: String
init(imageId: String) {
self.imageId = imageId
}
override func main() {
if self.isCancelled {
return
}
Alamofire.request(URL(string: "myURL")!,
method: .get)
.responseJSON(completionHandler: {(response) -> Void in
if let value = response.result.value{
let json = JSON(value).arrayValue
for item in json{
let url = URL(string: item["image"].string!)
self.image = try? Data(contentsOf: url!)
})
if self.isCancelled {
return
}
}
}
3) Third steps implement some methods inside viewController to manipulate the operations based on the visible cells.
func startDownload(_ image: imageId, indexPath: IndexPath) {
if let _ = pendingOperations.downloadsInProgress[indexPath] {
return
}
let downloader = ImageDownloader(image: imageId)
downloader.completionBlock = {
if downloader.isCancelled {
return
}
}
pendingOperations.downloadsInProgress.removeValue(forKey: indexPath)
pendingOperations.downloadsInProgress[indexPath] = downloader
pendingOperations.downloadQueue.addOperation(downloader)
}
}
func suspendAllOperation() {
pendingOperations.downloadQueue.isSuspended = true
}
func resumeAllOperation() {
pendingOperations.downloadQueue.isSuspended = false
}
func loadImages() {
let pathArray = collectionView.indexPathsForVisibleItems
let allPendingOperations =
Set(Array(pendingOperations.downloadsInProgress.keys))
let visiblePath = Set(pathArray!)
var cancelOperation = allPendingOperations
cancelOperation.subtract(visiblePath)
var startOperation = visiblePath
startOperation.subtract(allPendingOperations)
for indexpath in cancelOperation {
if let pendingDownload =
pendingOperations.downloadsInProgress[indexpath] {
pendingDownload.cancel()
}
pendingOperations.downloadsInProgress.removeValue(forKey: indexpath)
}
for indexpath in startOperation {
let indexPath = indexpath
startDownload(imageId, indexPath: indexPath)
}
}
4) Fourth steps to implement the scrollView delegate methods.
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
suspendAllOperation()
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
loadImages()
resumeAllOperation()
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if !decelerate {
loadImages()
resumeAllOperation()
}
}
func clearImageDownloaderOperations() {
if pendingOperations.downloadsInProgress.count > 0 {
pendingOperations.downloadsInProgress.removeAll()
pendingOperations.downloadQueue.cancelAllOperations()
}
}
Using Kingfisher 5 for prefetching, loading, showing and caching images we can solve the issue.
let url = URL(fileURLWithPath: path)
let provider = LocalFileImageDataProvider(fileURL: url)
imageView.kf.setImage(with: provider)
https://github.com/onevcat/Kingfisher/wiki/Cheat-Sheet#image-from-local-file
Related
I parsed a data from iTunes API with model:
struct AlbumData: Codable {
let results: [Result]
}
struct Result: Codable {
let artistName, collectionName: String
let trackCount: Int
let releaseDate: String
let artworkUrl100: String
}
struct AlbumModel {
let albumsResult: [Result]
}
and this is NetworkService code:
import Foundation
protocol NetworkServiceDelegate {
func updateInfo (_ manager: NetworkService, album: AlbumModel)
func errorInfo (error: Error)
}
struct NetworkService {
var delegate: NetworkServiceDelegate?
func fetchAlbums () {
let urlString = "https://itunes.apple.com/search?term=eminem&limit=8&entity=album"
performRequest(with: urlString)
}
func performRequest(with urlString: String) {
if let url = URL(string: urlString) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (data, response, error) in
if error != nil {
delegate?.errorInfo(error: error!)
print("Debuggg error \(LocalizedError.self)")
}
if let safeData = data {
if let albums = self.parseJSON(safeData) {
delegate?.updateInfo(self, album: albums)
print("succses - \(albums)")
}
}
}
task.resume()
}
}
func parseJSON(_ data: Data) -> AlbumModel? {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(AlbumData.self, from: data)
let result = decodedData.results
let album = AlbumModel(albumsResult: result)
return album
} catch {
print(error)
return nil
}
}
}
after that I appended this data into array:
var albums: [AlbumModel] = []
now I have array like this:
[Itunes_Albums.AlbumModel(albumsResult: [Itunes_Albums_.Result(artistName: "Eminem", collectionName: "The Eminem Show", trackCount: 20, releaseDate: "2002-01-01T08:00:00Z", artworkUrl100: "https://is5-ssl.mzstatic.com/image/thumb/Music115/v4/61/d5/0a/61d50a3d-4a27-187a-d16f-6b8ce4b62560/source/100x100bb.jpg"), Itunes_Albums_.Result(artistName: "Eminem", collectionName: "Recovery (Deluxe Edition)", trackCount: 19, releaseDate: "2010-06-21T07:00:00Z", artworkUrl100: "https://is5-ssl.mzstatic.com/image/thumb/Music125/v4/f1/40/ce/f140ce18-f176-7cf9-c220-3958d7747ae6/source/100x100bb.jpg")
After that I display this data into collection view
my SearchViewController
after that I want to write some characters to searchBar (name of album) and display that albums, but it doesn't works.
I can show all code:
import UIKit
class SearchViewController: UIViewController {
#IBOutlet weak var albumCollectionView: UICollectionView!
var albums: [AlbumModel] = []
var albumsForSearch: [AlbumModel] = []
var networkService = NetworkService()
override func viewDidLoad() {
super.viewDidLoad()
title = "Search"
registerCells()
networkService.delegate = self
networkService.fetchAlbums()
}
private func registerCells() {
albumCollectionView.register(UINib(nibName: AlbumCollectionViewCell.identifier, bundle: nil), forCellWithReuseIdentifier: AlbumCollectionViewCell.identifier)
}
}
//MARK: - AlbumManagerDelegate
extension SearchViewController: NetworkServiceDelegate {
func updateInfo(_ manager: NetworkService, album: AlbumModel) {
DispatchQueue.main.async {
self.albums.append(album)
self.albumsForSearch = self.albums
print("CHEEEEEK - \(self.albums)")
self.albumCollectionView.reloadData()
}
}
func errorInfo(error: Error) {
print(error)
}
}
//MARK: - UICollectionViewDelegate, UICollectionViewDataSource
extension SearchViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return albums.count
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return albums[section].albumsResult.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: AlbumCollectionViewCell.identifier, for: indexPath)
as! AlbumCollectionViewCell
let jData = albums[indexPath.section].albumsResult[indexPath.item]
cell.albumNameLabel.text = jData.collectionName
cell.artistNameLabel.text = jData.artistName
cell.numberOfTracksLabel.text = "\(jData.trackCount) song(s)"
cell.albumImage.load(urlString: "\(jData.artworkUrl100)")
cell.dateOfRelease.text = jData.releaseDate.substring(toIndex: 10)
return cell
}
}
//MARK: - UISearchBarDelegate
extension SearchViewController: UISearchBarDelegate, UISearchResultsUpdating {
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let searchView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "CollectionReusableView", for: indexPath)
return searchView
}
func updateSearchResults(for searchController: UISearchController) {
if let searchText = searchController.searchBar.text?.lowercased(), !searchText.isEmpty {
albumsForSearch = albums.filter { album -> Bool in
return album.albumsResult.contains { item -> Bool in item.collectionName.lowercased().contains(searchText)
}
}
albumCollectionView.reloadData()
} else {
albumsForSearch = albums
}
}
}
When I write some characters into searchBar nothing changes
Not sure what exactly it is you're struggling with so ill just point out a few things and hope this will solve your issue.
First, I don't know what your AlbumModel looks like. Perhaps you can show this code. but it looks like each instance contains more than one albumsResult. Are you expecting exactly one object in albumsResult in this array?
If not, I find this line really strange item.albumsResult[0]. That 0 there is a potential for crashes. So my answer doesn't assume there will always be one (And only one) item in that array.
Second, I also find this strange:
} else {
searching = false
albumsForSearch.removeAll()
albumsForSearch = albums
}
You're resetting your array every time you don't find a result. Not sure I can think of a valid reason why you would ever want to do that.
So perhaps this code is what you need:
if let searchText = let searchText = searchController.searchBar.text?.lowercased(), !searchText.isEmpty {
// Search text means filter results
albumsForSearch = albums.filter { album -> Bool in
return album.albumResult.contains { item -> Bool in
item.collectionName.lowercased().contains(searchText)
}
}
} else {
// No search text means display everything
albumsForSearch = albums
}
Then you just base your tableview sections and rows on albumsForSearch
My tableview gets slow when scrolling, I have a custom cell and a tableview:
I have this controller, where the api call is made and the array of trips is filled, then in cellForRowAt im creating the cell
class HistorialViewController: UIViewController , UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var historialTableView: UITableView!
var trips = [RootClass]()
override func viewDidLoad() {
super.viewDidLoad()
self.historialTableView.delegate = self
self.historialTableView.dataSource = self
self.historialTableView.register(CustomCellHistorial.self, forCellReuseIdentifier: cellId)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("coming back")
self.fetchTrips()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.historialTableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! CustomCellHistorial
let trip = self.trips[indexPath.row]
cell.trip = trip
return cell
}
private func fetchTrips(){
AFWrapper.getTripHistory( success: { (jsonResponse) in
self.trips = []
for item in jsonResponse["trips"].arrayValue {
self.trips.append(RootClass(fromJson:item))
}
self.reloadTrips()
Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false) { (nil) in
self.indicator.stopAnimating()
}
}, failure: { (error) -> Void in
print(error)
})
}
func reloadTrips(){
DispatchQueue.main.async{
self.historialTableView.reloadData()
}
}
This is my CustomCell
class CustomCellHistorial : UITableViewCell{
var trip: RootClass? {
didSet{
dateTimeLabel.text = returnCleanDate(fecha: trip!.createdAt)
distanceAndTimeLabel.text = returnDistanceAndTime(distance: (trip?.trip!.real!.dist!)!, time: (trip?.trip!.real!.time)!)
priceLabel.text = returnCleanPrice(price: (trip?.trip!.real!.price!)!)
ratedLabel.text = "Not Rated"
self.productImage.image = self.returnDriverImage(photoUrl: (self.trip?.driver!.photo!)!)
if (trip!.score) != nil {
let score = trip!.score
if (score?.driver) != nil{
if(trip!.score!.driver!.stars! != 0.0 ){
ratedLabel.isHidden = true
}else{
ratedLabel.isHidden = false
}
}else{
print("yei")
}
}
}
}
private func returnDriverImage(photoUrl: String) -> UIImage{
let url = URL(string: photoUrl)
do {
let data = try Data(contentsOf: url!)
if let roundedimg = UIImage(data: data){
let croppedImageDriver = roundedimg.resize(toTargetSize: self.productImage.frame.size)
return croppedImageDriver
}
} catch let error {
debugPrint("ERRor :: \(error)")
let image = UIImage(named: "perfilIcono")
return image!
}
let image = UIImage(named: "perfilIcono")
return image!
}
Answers that I have found are for older versions of Swift, and the way they make the tableview its in storyboard or they are not handling custom cells.
I think the problem is in the returnDriverImage function.
This line
let data = try Data(contentsOf: url!)
You call from
self.productImage.image = self.returnDriverImage(photoUrl: (self.trip?.driver!.photo!)!)
blocks the main thread and re downloads the same image multiple times when scroll , please consider using SDWebImage
I have tried so many ways to load image from server in collection view, but the lag is still there when scrolling!
this is my ImageProvider class:
let imageCache = NSCache<AnyObject, AnyObject>()
class ImageProvider: UIImageView {
var imageUrl: String?
func loadImage(from urlString: String) {
self.imageUrl = urlString
let url = URL(string: urlString)!
image = nil
if let imageFromCache = imageCache.object(forKey: urlString as AnyObject) as? UIImage {
self.image = imageFromCache
return
}
URLSession.shared.dataTask(with: url) { (data, request, error) in
if error != nil {
print(error!)
return
}
DispatchQueue.main.async {
let imageToCache = UIImage(data: data!)
if self.imageUrl == urlString {
self.image = imageToCache
}
imageCache.setObject(imageToCache!, forKey: urlString as AnyObject)
}
}.resume()
}
}
setting up data:
func setupCVData(cell: TrackCell, item: Audio) {
cell.title.text = item.name
cell.url = URL(string: item.url!)!
if let url = cell.url {
var imageLink = String()
(_, _, imageLink) = fetcher.getData(from: url)
cell.imageUrl = imageLink
cell.updateImageView()
}
}
and updateImageView function:
func updateImageView() {
self.artwork.loadImage(from: imageUrl)
}
I've tried calling setupCVData in collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) and collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath)
I also used SDWebImage with no luck!
So what am I doing wrong here? I can't seem to figure out the problem.
As far as I know, you don't have to create the UIImage object on the main thread. I usually create UIImages in background threads without any problems. UI elements should only be modified on the main thread, however. So when you set the UIImageView's image property, make sure that's done on the main thread.
let imageToCache = UIImage(data: data!)
imageCache.setObject(imageToCache!, forKey: urlString as AnyObject)
DispatchQueue.main.async {
if self.imageUrl == urlString {
self.image = imageToCache
}
}
I created a tableView with custom cells that each cell has an image.
In the model class, I created a func mainPulatesData() to use URLSession dataTask method to retrieve data from url, and convert data into UIImage in the completion handler block, then add image into an variable of array of UIImage.
The process of retrieve data and adding them into UIImage array was perform in DispatchQueue.global(qos: .userInitiated).async block. based on the print message, the images did be added into array.
however, even I created an instance of model class in tableView controller, and invokes the mainPulatesData() in viewDidlLoad, the image didn't show up in the table.
Based on other print message in table view controller class, I found even it can be added into array in model class, but it seems like doesn't work on the instance of model class in tableView controller.
that's the code in model class to gain image data:
func mainPulatesData() {
let session = URLSession.shared
if myURLs.count > 0{
print("\(myURLs.count) urls")
for url in myURLs{
let task = session.dataTask(with: url, completionHandler: { (data,response, error) in
let imageData = data
DispatchQueue.global(qos: .userInitiated).async {
if imageData != nil{
if let image = UIImage(data: imageData!){
self.imageList.append(image)
print("\(self.imageList.count) images added.")
}
}
else{
print("nil")
}
}
})
task.resume()
}
}
}
that's the code in view controller to create instance of model:
override func viewDidLoad() {
super.viewDidLoad()
myModel.mainPulatesURLs()
myModel.mainPulatesData()
loadImages()
}
private func loadImages(){
if myModel.imageList.count > 0{
tableView.reloadData()
}
else{
print("data nil")
}
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myModel.imageList.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "imageCell", for: indexPath) as! ImageTableViewCell
if myModel.imageList.count > 0{
let image = myModel.imageList[indexPath.row]
cell.tableImage = image
return cell
}
return cell
}
The reason is that the images or imageList isn't ready by the time cellForRowAt is called after you reloadData().
A good practice is to use placeholder images in the beginning and only load image when a table view cell is visible instead of everything at once. Something like:
// VC class
private var modelList = [MyModel(url: url1), MyModel(url: url2), ...]
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return modelList.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "imageCell", for: indexPath) as! ImageTableViewCell
cell.update(model: modelList[indexPath.row])
return cell
}
// Cell class
#IBOutlet weak var cellImageView: UIImageView!
func update(model: MyModel) {
model.fetchImage(callback: { image in
self.cellImageView.image = image
})
}
// Model class
final class MyModel: NSObject {
let url: URL
private var _imageCache: UIImage?
init(url: URL) {
self.url = url
}
func fetchImage(callback: #escaping (UIImage?) -> Void) {
if let img = self._imageCache {
callback(img)
return
}
let task = URLSession.shared.dataTask(with: self.url, completionHandler: { data, _, _ in
if let imageData = data, let img = UIImage(data: imageData) {
self._imageCache = img
callback(img)
} else {
callback(nil)
}
})
task.resume()
}
}
Images are not displayed because you download them in background thread (asynchronously), and loadImages() is called synchronously. That means loadImage() is called before myModel.mainPulatesData() is executed, so when your images are downloaded, tableview is not being updated (reloadData() is not called). You should create Protocol to notify UIViewController, that data has been downloaded, or use Completion Handler.
Simple example of handler I am using, I call this in my viewDidLoad, it requests data from server and return an array of [Reservation]?
ReservationTableModule.requestAllReservations { [weak self] reservations in
guard let `self` = self else {
return
}
guard let `reservations` = `reservations` else {
return
}
self.reservations = reservations
.reservationsTableView.reloadData()
}
this is actual request function
class func requestAllReservations(handler: #escaping ([Reservation]?) -> Void) {
let url = "reservations/all"
APIModel.shared.requestWithLocation(.post, URL: url, parameters: nil) { data in
let reservations = data?["reservations"].to(type: Reservation.self) as? [Reservation]
handler(reservations)
}
}
handler: #escaping ([Reservation]?) -> Void is called completion handler, you should, I guess make it handler: #escaping ([UIImage]?) -> Void and after your data downloaded call handler(reservations)
You should take note of the sequence of your function call here:
myModel.mainPulatesURLs() --> populates myURLs
myModel.mainPulatesData() --> populates image from myURLs in forloop asynchronously.
loadImages() --> called asynchronously.
while you're loading your images from myModel.mainPulatesData() you already called loadImages() which myModel.imageList was still empty.
you should call loadImages() after a callback from myModel.mainPulatesData() or when you're sure that the images where already loaded.
you can use dispatch_group_t to configure the callbacks.
here as requested:
import UIKit
var myURLs: [String] = ["urlA", "urlB", "urlC", "urlD"]
// we define the group for our asynchronous fetch. also works in synchronous config
var fetchGroup = DispatchGroup()
for urlString in myURLs {
// for every url fetch we define, we will call an 'enter' to issue that there is a new block for us to wait or monitor
fetchGroup.enter()
// the fetch goes here
let url = URL(string: urlString)!
URLSession.shared.downloadTask(with: URLRequest(url: url), completionHandler: { (urlReq, urlRes, error) in
// do your download config here...
// now that the block has downloaded the image, we are to notify that it is done by calling 'leave'
fetchGroup.leave()
}).resume()
}
// now this is where our config will be used.
fetchGroup.notify(queue: DispatchQueue.main) {
// reload your table here as all of the image were fetched regardless of download error.
}
Here is my Movie class:
import UIKit
class Movie {
var title: String = ""
var rating: Double = 0
var image: UIImage = UIImage()
}
I want to load the the array of movie to tableView, I have tried like this:
import UIKit
import Cosmos
import Alamofire
//import AlamofireImage
import SwiftyJSON
class Downloader {
class func downloadImageWithURL(url:String) -> UIImage! {
let data = NSData(contentsOfURL: NSURL(string: url)!)
return UIImage(data: data!)
}
}
class MovieViewController: UIViewController, UITableViewDataSource {
#IBOutlet var tableView: UITableView!
var movies: [Movie] = []
override func viewDidLoad() {
super.viewDidLoad()
fetchMovies()
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! MovieTableViewCell
let movie = movies[indexPath.row]
cell.movieTitleLabel.text = movie.title
cell.movieRatingView.rating = Double(movie.rating / 20)
cell.movieImageView.image = movie.image
return cell
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return movies.count
}
func fetchMovies() {
let movieURLString = "https://coderschool-movies.herokuapp.com/movies?api_key=xja087zcvxljadsflh214"
Alamofire.request(.GET, movieURLString).responseJSON { response in
let json = JSON(response.result.value!)
let movies = json["movies"].arrayValue
let queue = NSOperationQueue()
for movie in movies {
let title = movie["title"].string
let rating = movie["ratings"]["audience_score"].double
let imageURLString = movie["posters"]["thumbnail"].string
let movie = Movie()
movie.title = title!
movie.rating = rating!
let operation = NSBlockOperation(block: {
movie.image = Downloader.downloadImageWithURL(imageURLString!)
})
queue.addOperation(operation)
self.movies.append(movie)
self.tableView.reloadData()
}
}
}
}
The problem is that: when I scroll down or up, the images will be reload, otherwise they are not reloaded. Only the title and rating will be
I only know the reason is these line
self.movies.append(movie)
self.tableView.reloadData()
are compiled before
let operation = NSBlockOperation(block: {
movie.image = Downloader.downloadImageWithURL(imageURLString!)
})
queue.addOperation(operation)
But if I scroll down, it will like this:
I've already do it perfectly with AlamofireImage, but I really want to use NSOperation for diving with it.
I think you should make sure to reloadData() on the main thread. By default it's happening on a background thread and cannot update the UI.
dispatch_async(dispatch_get_main_queue(),{
self.tableView.reloadData()
})
I solved the problem by put the reloadData to the main thread, like so:
let operation = NSBlockOperation(block: {
movie.image = Downloader.downloadImageWithURL(imageURLString!)
NSOperationQueue.mainQueue().addOperationWithBlock() {
self.tableView.reloadData()
}
})
Now it works well.