I can't use value inside my UICollectionView from API ( Swift, MVVM, UIKit)

I'm trying to make simple app that shows a list with values from API. The thing is that I can't use that same way like previous projects. I got a error message:

failure(Swift.DecodingError.typeMismatch(Swift.Array<Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array<Any> but found a dictionary instead.", underlyingError: nil)))
  1. ViewController
import UIKit
import Combine

class PokedexViewController: UIViewController {
    
    var subscriptions = Set<AnyCancellable>()
    private var pokedexListVM = PokedexListViewModel()
    
    var collectionView: UICollectionView?

    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = .systemBackground
        setupCollectionView()
        
    }
    
    private func setupCollectionView() {
        
        let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
        layout.sectionInset = UIEdgeInsets(top: 5, left: 5, bottom: 0, right: 5)
        layout.itemSize = CGSize(width: view.bounds.size.width, height: 60)
        
        collectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
        collectionView?.register(PokedexCollectionViewCell.self, forCellWithReuseIdentifier: PokedexCollectionViewCell.identifier)
        
        collectionView?.delegate = self
        collectionView?.dataSource = self
        
        view.addSubview(collectionView ?? UICollectionView())
        //self.view = view
        setupViewModel()
        
    }
    
    private func setupViewModel() {
        
        pokedexListVM.$pokemons
            .receive(on: RunLoop.main)
            .sink(receiveValue: { [weak self] _ in
                self?.collectionView!.reloadData()
            }).store(in: &subscriptions)
        
        let stateHandler: (PokedexListViewModelState) -> Void = { state in
            switch state {
            case .loading:
                print("loading")
            case .finishLoading:
                print("finish")
            case .error:
                print("error")
            }
        }
        
        pokedexListVM.$state
            .receive(on: RunLoop.main)
            .sink(receiveValue: stateHandler)
            .store(in: &subscriptions)
        
    }
    
}

extension PokedexViewController: UICollectionViewDelegate, UICollectionViewDataSource {
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return pokedexListVM.pokemons.count
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
         guard let cell = collectionView.dequeueReusableCell(
            withReuseIdentifier: PokedexCollectionViewCell.identifier,
            for: indexPath
         ) as? PokedexCollectionViewCell else {
            return UICollectionViewCell()
        }
        
        cell.configure(with: "\(pokedexListVM.pokemons[indexPath.row].name)")
        
        return cell
    }
    
}

  1. ViewModel
    import Foundation
import Combine

enum PokedexListViewModelState {
    case loading
    case finishLoading
    case error
}

class PokedexListViewModel: ObservableObject {
    
    @Published private(set) var pokemons: [Pokedex.Results] = []
    @Published private(set) var state: PokedexListViewModelState = .loading
    private var subscriptions = Set<AnyCancellable>()
    private var url = "https://pokeapi.co/api/v2/pokemon?offset=0&limit=898"
    //private let url = "https://jsonplaceholder.typicode.com/posts/"
    
    init() {
        loadPokemons()
    }
    
    private func loadPokemons() {
        
        state = .loading
        
        let valueHandler: ([Pokedex.Results]) -> Void = { [weak self] items in
            self?.pokemons = items
        }
        
        let completionHandler: (Subscribers.Completion<Error>) -> Void = { [weak self] completion in
            switch completion {
            case .failure:
                self?.state = .error
                print(completion)
            case .finished:
                self?.state = .finishLoading
            }
        }
        
        URLSession.shared.dataTaskPublisher(for: URL(string: url)!)
            .map{ $0.data }
            .decode(type: [Pokedex.Results].self, decoder: JSONDecoder())
            .sink(receiveCompletion: completionHandler, receiveValue: valueHandler)
            .store(in: &subscriptions)
        
    }
}
  1. Model
import Foundation

struct Pokedex: Codable {
    
    struct Results: Codable {
        let name: String
        let url: String
    }

    var results: [Results]
    var count: Int
    var next: String
    var previous: String?
    
}

I use pokeAPIV2 from that link: https://pokeapi.co/api/v2/pokemon?offset=0&limit=898

{
"count": 1154,
"next": "https://pokeapi.co/api/v2/pokemon?offset=3&limit=3",
"previous": null,
"results": [
{
"name": "bulbasaur",
"url": "https://pokeapi.co/api/v2/pokemon/1/"
},
{
"name": "ivysaur",
"url": "https://pokeapi.co/api/v2/pokemon/2/"
},
{
"name": "venusaur",
"url": "https://pokeapi.co/api/v2/pokemon/3/"
}
]
}

I want to use the "name" data from that API. One thing that I noticed is that API starts from "{" and previous ones starts from "[".

  • Your json example is a single item (your Pokedex?), which contains an array, called "results"
  • You are trying to decode an array of Results
  • That isn't going to work!

Try

 .decode(type: Pokedex.self, decoder: JSONDecoder())

Alternatively, if your json is actually an array of the sample shown, try:

 .decode(type: [Pokedex].self, decoder: JSONDecoder())

Best to mark this question as answered, and post two new questions.

@Jaanioo 

A comment on this code. For readability, it would be better to name Results as Result. That's what you wrote naturally in your comment.

    struct Result: Codable {
        let name: String
        let url: String
    }

For the next posts :

how can I call name properity inside my PokedexCollectionView ? Right now i can call only whole Result struct

  • What is the issue of calling the whole Result struct ?
  • Where is it in your code ?
I can't use value inside my UICollectionView from API ( Swift, MVVM, UIKit)
 
 
Q