Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 9 additions & 22 deletions TMDB/Sources/Screens/Search/MovieTableViewCell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,32 +17,19 @@ class MovieTableViewCell: UITableViewCell, NibProvidable, ReusableView {
@IBOutlet private var poster: UIImageView!
private var cancellable: AnyCancellable?

override func prepareForReuse() {
super.prepareForReuse()
cancelImageLoading()
}

func bind(to viewModel: MovieViewModel) {
cancelImageLoading()
title.text = viewModel.title
subtitle.text = viewModel.subtitle
rating.text = viewModel.rating
cancellable = viewModel.poster.sink { [unowned self] image in self.showImage(image: image) }

cancellable = viewModel.poster
.receive(on: DispatchQueue.main)
.assign(to: \.poster.image, on: self)
}

private func showImage(image: UIImage?) {
cancelImageLoading()
UIView.transition(with: self.poster,
duration: 0.3,
options: [.curveEaseOut, .transitionCrossDissolve],
animations: {
self.poster.image = image
})
}

private func cancelImageLoading() {
poster.image = nil
cancellable?.cancel()
}


private func cancelImageLoading() {
poster.image = nil
cancellable?.cancel()
}
}
20 changes: 11 additions & 9 deletions TMDB/Sources/Services/ImageLoader/ImageLoaderService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,23 @@ import UIKit.UIImage
import Combine

final class ImageLoaderService: ImageLoaderServiceType {

private let cache: ImageCacheType = ImageCache()

func loadImage(from url: URL) -> AnyPublisher<UIImage?, Never> {
func loadImage(from url: URL, placeholder: UIImage? = nil) -> AnyPublisher<UIImage?, Never> {
if let image = cache.image(for: url) {
return .just(image)
}
return URLSession.shared.dataTaskPublisher(for: url)
.map { (data, response) -> UIImage? in return UIImage(data: data) }
.catch { error in return Just(nil) }
.handleEvents(receiveOutput: {[unowned self] image in
guard let image = image else { return }
.tryMap { data, response -> UIImage in
guard let image = UIImage(data: data) else {
throw NSError(domain: "", code: 1, userInfo: nil)
}

self.cache.insertImage(image, for: url)
})
.print("Image loading \(url):")
return image
}
.replaceError(with: placeholder)
.eraseToAnyPublisher()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ import UIKit.UIImage
import Combine

protocol ImageLoaderServiceType: AnyObject, AutoMockable {
func loadImage(from url: URL) -> AnyPublisher<UIImage?, Never>
func loadImage(from url: URL, placeholder: UIImage?) -> AnyPublisher<UIImage?, Never>
}
25 changes: 14 additions & 11 deletions TMDB/Sources/UseCases/MoviesUseCase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,19 @@ final class MoviesUseCase: MoviesUseCaseType {
}

func loadImage(for movie: Movie, size: ImageSize) -> AnyPublisher<UIImage?, Never> {
return Deferred { return Just(movie.poster) }
.flatMap({[unowned self] poster -> AnyPublisher<UIImage?, Never> in
guard let poster = movie.poster else { return .just(nil) }
let url = size.url.appendingPathComponent(poster)
return self.imageLoaderService.loadImage(from: url)
})
.subscribe(on: Scheduler.backgroundWorkScheduler)
.receive(on: Scheduler.mainScheduler)
.share()
.eraseToAnyPublisher()
guard let poster = movie.poster else { return .just(nil) }
let url = size.url.appendingPathComponent(poster)
return imageLoaderService.loadImage(from: url, placeholder: imageFrom(color: .gray))
}

func imageFrom(color: UIColor) -> UIImage {
let rect = CGRect(x: 0, y: 0, width: 1, height: 1)
UIGraphicsBeginImageContext(rect.size)
let context = UIGraphicsGetCurrentContext()
context!.setFillColor(color.cgColor)
context!.fill(rect)
let img = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return img!
}

}