I am trying to display the data into UI collection view .I am following the MVVM Architecture pattern. I am following programatic approach . I put the break point , I can see onto console , The data is decoded from URL but problem is it not displaying the into view controller.
Here is the my network manager code .
import Foundation
protocol NetworkManagerProtocol{
func getModel<T: Codable>(_ model: T.Type, from url: String, completion: @escaping (Result<T, NetworkError>) -> Void)
func getData(from url: String, completion: @escaping (Result<Data, NetworkError>) -> Void)
}
class NetworkManager: NetworkManagerProtocol{
func getModel<T: Codable>(_ model: T.Type, from url: String, completion: @escaping (Result<T, NetworkError>) -> Void) {
guard let url = URL(string: url) else {
completion(.failure(.badURL))
return
}
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
do {
let response = try JSONDecoder().decode(model, from: data)
completion(.success(response))
} catch let error {
completion(.failure(.other(error)))
}
}
if let error = error {
completion(.failure(.other(error)))
}
}
.resume()
}
func getData(from url: String, completion: @escaping (Result<Data, NetworkError>) -> Void) {
guard let url = URL(string: url) else {
completion(.failure(.badURL))
return
}
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
completion(.success(data))
}
if let error = error {
completion(.failure(.other(error)))
}
}
.resume()
}
}
Here is the View Model code.
import Foundation
class PhotoViewModel {
private let networkManager: NetworkManagerProtocol
var hits = [Hit]()
private var cache = [String: Data]()
private weak var delegate: PhotoViewable?
init(networkManager: NetworkManagerProtocol) {
self.networkManager = networkManager
}
var rows: Int {
return hits.count
}
func fecthPhotoRecord(){
let networkUrl = NetworkURLs.baseURL
networkManager.getModel(Photo.self, from: networkUrl) { [weak self] result in
switch result{
case.success(let photo):
self?.hits = photo.hits
self?.delegate?.refreshUI()
case.failure(let error):
print(error)
self?.delegate?.showError()
}
}
}
func downloadImage(row: Int, completion: @escaping (Data) -> Void) {
let hit = hits[row]
let hitpath = hit.previewURL
if let data = cache[hitpath] {
completion(data)
return
}
networkManager
.getData(from: hitpath) { result in
switch result {
case .success(let data):
self.cache[hitpath] = data
DispatchQueue.main.async {
completion(data)
}
case .failure(let error):
print(error)
}
}
}
}
Here is the collection view cell ..
import UIKit
protocol ReusableView: AnyObject {
static var identifier: String { get }
}
class PhotoCollectionViewCell: UICollectionViewCell {
private lazy var imageView: UIImageView = {
let imageView = UIImageView()
imageView.frame = CGRect(x:0, y:0, width:50, height:50)
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
private lazy var tagLabel:UILabel = {
let label = UILabel()
label.font = UIFont.boldSystemFont(ofSize: 15)
label.textColor = .black
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
imageView.translatesAutoresizingMaskIntoConstraints = false
self.contentView.addSubview(tagLabel)
self.contentView.addSubview(imageView)
NSLayoutConstraint.activate([
tagLabel.topAnchor.constraint(equalTo: self.contentView.topAnchor),
tagLabel.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor),
tagLabel.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor),
tagLabel.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor),
imageView.topAnchor.constraint(equalTo: self.tagLabel.topAnchor),
imageView.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor),
imageView.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor),
])
self.contentView.backgroundColor = .lightGray
}
func configureCell(tagName: String) {
tagLabel.text = tagName
}
func configureImageCell(row: Int, viewModel: PhotoViewModel) {
imageView.image = nil
viewModel
.downloadImage(row: row) { [weak self] data in
let image = UIImage(data: data)
self?.imageView.image = image
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension PhotoCollectionViewCell: ReusableView {
static var identifier: String {
return String(describing: self)
}
}
Here is the code for view controller .
import UIKit
protocol PhotoViewable: AnyObject {
func refreshUI()
func showError()
}
class PhotoViewController: UIViewController {
var viewModel : PhotoViewModel
init(viewModel : PhotoViewModel) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
}
private let collectionView: UICollectionView = {
let viewLayout = UICollectionViewFlowLayout()
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: viewLayout)
collectionView.backgroundColor = .white
return collectionView
}()
private enum LayoutConstant {
static let spacing: CGFloat = 16.0
static let itemHeight: CGFloat = 300.0
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private var activityIndicator: UIActivityIndicatorView = {
let activityIndicator = UIActivityIndicatorView()
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
return activityIndicator
}()
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
setupLayouts()
viewModel.fecthPhotoRecord()
collectionView.delegate = self
collectionView.dataSource = self
view.backgroundColor = .lightGray
collectionView.reloadData()
}
private func setupViews() {
view.backgroundColor = .white
view.addSubview(collectionView)
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(PhotoCollectionViewCell.self, forCellWithReuseIdentifier: PhotoCollectionViewCell.identifier)
}
private func setupLayouts() {
collectionView.translatesAutoresizingMaskIntoConstraints = false
// Layout constraints for `collectionView`
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
collectionView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
collectionView.leftAnchor.constraint(equalTo: view.leftAnchor),
collectionView.rightAnchor.constraint(equalTo: view.rightAnchor)
])
}
}
extension PhotoViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return viewModel.hits.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: PhotoCollectionViewCell.identifier, for: indexPath) as! PhotoCollectionViewCell
let row = indexPath.row
let hits = viewModel.hits[indexPath.row]
cell.configureCell(tagName: hits.tags)
cell.configureImageCell(row: row, viewModel: viewModel)
return cell
}
}
extension PhotoViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = itemWidth(for: view.frame.width, spacing: LayoutConstant.spacing)
return CGSize(width: width, height: LayoutConstant.itemHeight)
}
func itemWidth(for width: CGFloat, spacing: CGFloat) -> CGFloat {
let itemsInRow: CGFloat = 2
let totalSpacing: CGFloat = 2 * spacing (itemsInRow - 1) * spacing
let finalWidth = (width - totalSpacing) / itemsInRow
return floor(finalWidth)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: LayoutConstant.spacing, left: LayoutConstant.spacing, bottom: LayoutConstant.spacing, right: LayoutConstant.spacing)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return LayoutConstant.spacing
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return LayoutConstant.spacing
}
}
extension PhotoViewController: PhotoViewable {
func refreshUI() {
DispatchQueue.main.async {
self.collectionView.isHidden = false
// self.activityIndicator.stopAnimating()
self.collectionView.reloadData()
}
}
func showError() {
DispatchQueue.main.async {
// self.activityIndicator.stopAnimating()
self.collectionView.reloadData()
}
}
}
Here is the console .
Here is the result of the screenshot.
CodePudding user response:
The problem is you are not setting the delegate for viewModel
add the following line in viewDidLoad
before calling viewModel.fecthPhotoRecord()
viewModel.delegate = self
CodePudding user response:
main problem is you didn't call the delegate for your viewModel before fetching the data,
always setup you delegate before any data management in viewDidLoad
viewModel.delegate = self
viewModel.datasource = self //if needed datasource