How to implement simple MVC design pattern in Swift?

2.6k views Asked by At

I am new to MVC design pattern. I created "DataModel" it will make an API call, create data, and return data to the ViewController using Delegation and "DataModelItem" that will hold all data. How to call a DataModel init function in "requestData" function. Here is my code:

protocol DataModelDelegate:class {
    func didRecieveDataUpdata(data:[DataModelItem])
    func didFailUpdateWithError(error:Error)
}

class DataModel: NSObject {
    weak var delegate : DataModelDelegate?
    func requestData() {

    }
    private func setDataWithResponse(response:[AnyObject]){
        var data = [DataModelItem]()
        for item in response{
            if let tableViewModel = DataModelItem(data: item as? [String : String]){
                data.append(tableViewModel)
            }
        }
        delegate?.didRecieveDataUpdata(data: data)
    }
}

And for DataModelItem:

class DataModelItem{
    var name:String?
    var id:String?

    init?(data:[String:String]?) {
        if let data = data, let serviceName = data["name"] , let serviceId = data["id"] {
            self.name = serviceName
            self.id = serviceId
        }
        else{
            return nil
        }
    }
}

Controller:

class ViewController: UIViewController {
    private let dataSource = DataModel()
    override func viewDidLoad() {
        super.viewDidLoad()
        dataSource.delegate = self
    }

    override func viewWillAppear(_ animated: Bool) {
        dataSource.requestData()
    }

}
extension ViewController : DataModelDelegate{
    func didRecieveDataUpdata(data: [DataModelItem]) {
        print(data)
    }

    func didFailUpdateWithError(error: Error) {
        print("error:  \(error.localizedDescription)")
    }


}
3

There are 3 answers

1
HariKarthick On BEST ANSWER

I am just sharing my answer here and I am using a codable. It will be useful for anyone:

Model:

import Foundation

struct DataModelItem: Codable{
    struct Result : Codable {
        let icon : String?
        let name : String?
        let rating : Float?
        let userRatingsTotal : Int?
        let vicinity : String?

        enum CodingKeys: String, CodingKey {
            case icon = "icon"
            case name = "name"
            case rating = "rating"
            case userRatingsTotal = "user_ratings_total"
            case vicinity = "vicinity"
        }
    }
    let results : [Result]?
}

NetWork Layer :

import UIKit

protocol DataModelDelegate:class {
    func didRecieveDataUpdata(data:[String])
    func didFailUpdateWithError(error:Error)
}

class DataModel: NSObject {
    weak var delegate : DataModelDelegate?
    var theatreNameArray = [String]()
    var theatreVicinityArray = [String]()
    var theatreiconArray = [String]()
    func requestData() {
        Service.sharedInstance.getClassList { (response, error) in
            if error != nil {
                self.delegate?.didFailUpdateWithError(error: error!)
            } else if let response = response{
                self.setDataWithResponse(response: response as [DataModelItem])
            }
        }
    }

    private func setDataWithResponse(response:[DataModelItem]){
        for i in response[0].results!{
            self.theatreNameArray.append(i.name!)
            self.theatreVicinityArray.append(i.vicinity!)
            self.theatreiconArray.append(i.icon!)

        }
        delegate?.didRecieveDataUpdata(data: theatreNameArray)
        print("TheatreName------------------------->\(self.theatreNameArray)")
        print("TheatreVicinity------------------------->\(self.theatreVicinityArray)")
        print("Theatreicon------------------------->\(self.theatreiconArray)")

    }
}

Controller :

class ViewController: UIViewController {
    private let dataSource = DataModel()
    override func viewDidLoad() {
        super.viewDidLoad()
        dataSource.delegate = self
    }

    override func viewWillAppear(_ animated: Bool) {
        dataSource.requestData()
    }

}
extension ViewController : DataModelDelegate{
    func didRecieveDataUpdata(data: [DataModelItem]) {
        print(data)
    }

    func didFailUpdateWithError(error: Error) {
        print("error:  \(error.localizedDescription)")
    }


}

APIManager :

class Service : NSObject{
    static let sharedInstance = Service()    
    func getClassList(completion: (([DataModelItem]?, NSError?) -> Void)?) {
        guard let gitUrl = URL(string: "") else { return }
        URLSession.shared.dataTask(with: gitUrl) { (data, response
            , error) in
            guard let data = data else { return }
            do {
                let decoder = JSONDecoder()
                let gitData = try decoder.decode(DataModelItem.self, from: data)
                completion!([gitData],nil)

            } catch let err {
                print("Err", err)
                completion!(nil,err as NSError)
            }
            }.resume()
    }
}
7
Abhinav Saldi On

I would recommend using a singleton instance for DataModel, since this would be a class you would be invoking from many points in your application. You may refer its documentation at : Managing Shared resources using singleton With this you wont need to initialise this class instance every time you need to access data.

3
Ahmad F On

How to implement simple MVC design pattern in Swift?

As a generic answer, in iOS development you're already doing this implicitly! Dealing with storyboard(s) implies the view layer and controlling the logic of how they work and how they are connected to the model is done by creating view controller, that's the default flow.

For your case, let's clarify a point which is: according to the standard MVC, by default the responsible layer for calling an api should be -logically- the view controller. However for the purpose of modularity, reusability and avoiding to create massive view controllers we can follow the approach that you are imitate, that doesn't mean that its the model responsibility, we can consider it a secondary helper layer (MVC-N for instance), which means (based on your code) is DataModel is not a model, its a "networking" layer and DataModelItem is the actual model.


How to call a DataModel init function in "requestData" function

It seems to me that it doesn't make scene. What do you need instead is an instance from DataModel therefore you could call the desired method.

In the view controller:

let object = DataModel()
object.delegate = self // if you want to handle it in the view controller itself
object.requestData()