0

I am working to make an app that shows some companies in a collection view. I am fetching these companies from an API. I want to use MVVM and Protocol Oriented build. But when I run my app, the error occurs saying:

Property 'self.viewModel' not initialized at super.init call

How can I fix this error?

Here is my API Manager:

enum APIError : Error {
    case URLError
    case ConnectionError
}

class APIManager: APIProtocol {
    func fetchData(completion : @escaping(Result<[Company],APIError>)->Void) {
        guard let url = URL(string: "https://fakerapi.it/api/v1/companies?    _quantity=20") else {return}
        URLSession.shared.dataTask(with: url) { (data, _, error) in
            guard let data = data else {
                completion(.failure(.URLError))
                return
            }
            DispatchQueue.main.async {
                do {
                    let jsonData = try? JSONDecoder().decode(InitialData.self, from: data)
                    guard let jsonData = jsonData else {return}
                    print(jsonData.data)
                    completion(.success(jsonData.data))
                }
            }
        }.resume()
    }
}

And here is my API protocol:

protocol APIProtocol {
    func fetchData(completion : @escaping(Result<[Company],APIError>)->Void)
}

I want to build a complex API manager. I don't think the problem is about APIManager.

Here is my ViewModel file:

class CompanyViewModel {
    private let apiProtocol : APIProtocol
    weak var output : CompanyViewModelOutput?
    
    init(apiProtocol: APIProtocol) {
        self.apiProtocol = apiProtocol
    }
    
    func fetchData() {
        apiProtocol.fetchData { [weak self] result in
            switch result {
            case .success(let companyList):
                self?.output?.fetchList(companyList: companyList)
                print(companyList)
            case .failure(let error):
                print(error.localizedDescription)
                
            }
        }
    }
}

And the output protocol:

protocol CompanyViewModelOutput : AnyObject {
    func fetchList(companyList : [Company])
}

I want to fetch company list in viewmodel and make a cleaner view controller file.

Here is my view controller:

class ViewController: UIViewController, CompanyViewModelOutput {
    func fetchList(companyList: [Company]) {
        self.compList = companyList
    }
    
    var compList = [Company]() {
        didSet {
            self.collectionView.reloadData()
        }
    }

    @IBOutlet weak var collectionView: UICollectionView!
    private let viewModel : CompanyViewModel
    
    init(viewModel: CompanyViewModel) {
        self.viewModel = viewModel
        super.init(nibName: nil, bundle: nil)
        self.viewModel.output = self
    }
    
    required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder) 
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        collectionView.delegate = self
        collectionView.dataSource = self
        // Do any additional setup after loading the view.
        viewModel.fetchData()
    }
}

extension ViewController : UICollectionViewDelegate, UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection      section: Int) -> Int {
        return compList.count
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! CompanyCell
        cell.nameLabel.text = compList[indexPath.row].name
        cell.countryLabel.text = compList[indexPath.row].country
        return cell
    }
}

I initialized all properties in sceneDelegate:

guard let scene = (scene as? UIWindowScene) else { return }
window = UIWindow(windowScene: scene)
let apiProtocol : APIProtocol = APIManager()
let viewModel = CompanyViewModel(apiProtocol: apiProtocol)
window?.rootViewController = ViewController(viewModel: viewModel)
window?.makeKeyAndVisible()

I don't understand why this error occurs. I am new at Swift and I am trying to learn protocol oriented programming. Can anyone help me with this?

HangarRash
  • 7,314
  • 5
  • 5
  • 32
  • I guess you are working with a storyboard and is creating your view controller incorrectly, look at [this question](https://stackoverflow.com/questions/33374272/how-to-set-a-new-root-view-controller) for example. The actual error is cause by the `required` init being called instead of the one you expected. – Joakim Danielson Mar 05 '23 at 20:50

1 Answers1

1

The compiler error is being caused in:

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder) 
}

because this initializer is not initializing all properties (such as viewModel).

Since you don't appear to be creating ViewController via storyboard, this init isn't needed but it is required. A common way to workaround this is to code this init as follows:

required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

This will allow the code to compile and your app to run. But this does assume that you do not need this init for a storyboard.

HangarRash
  • 7,314
  • 5
  • 5
  • 32