0

Please can somebody help me. I spent about 1 week, trying to resolve it by myself. I got tableview and MVC paradigm. I can't setup cellForRow and numbersOfSections. Because it like extension in UIView, but I can't save any properties in UIView due to MVC paradigm. Can somebody explain me how I can resolve this problem?

class ShopView: UIView {

private(set) lazy var productListTableView: UITableView = {
    let productList = UITableView()
    productList.dataSource = self
    productList.translatesAutoresizingMaskIntoConstraints = false
    return productList
}()

override init(frame: CGRect) {
    super.init(frame: frame)
    self.setupLayout()
}

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

class ShopViewControllerModel: UIViewController {

weak var coordinator: MainCoordinator?

let requestFactory = RequestFactory()

private var shopTableView: ShopView  {
    return self.view as! ShopView
}

override func viewDidLoad() {
    super.viewDidLoad()
}

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
}

override func loadView() {
    super.loadView()
    self.view = ShopView()
}
}


extension ShopViewControllerModel {
{
    func download(completion: @escaping (Product) -> ()) {
        let getGoods = requestFactory.getGoods()
        getGoods.getNewGoods(pageNumber: 1, id_category: 1) { response in
            switch response.result {
            case .success(let goods):
                for index in goods.products {
                    let array = Product(product_name: index.product_name, id: index.id, price: index.price)
                    completion(array)
                }
            case .failure(let error):
                print(error.localizedDescription)
            }
        }
    }
}

class ShopViewController: UIViewController {

lazy var shopTableView = ShopViewControllerModel()
weak var coordinator: MainCoordinator?
var totalGoodsModel: [Product] = []



override func viewDidLoad() {
    super.viewDidLoad()
    self.title = "Shop"
    self.navigationController?.navigationBar.isHidden = true
    self.view.backgroundColor = .red
    addEditVC()

}

private func addEditVC() {
    self.shopTableView.download { (response) in
        self.totalGoodsModel = [response]

        print(self.totalGoodsModel)
    }
}

extension ShopView: UITableViewDataSource {

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        let sections = 2
        return sections
    }



    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        self.productListTableView.register(UITableViewCell.self, forCellReuseIdentifier: "contactCell")
        let cell = tableView.dequeueReusableCell(withIdentifier: "contactCell", for: indexPath) as! ShopTableViewCell
        cell.textLabel?.text = "ShopTableViewCell"
        return cell
    }
}
4
  • 1
    Why do you need to subclass UIView? Why not make your table view a property of your view controller, this is the normal way to do it? And why do you have two view controllers? Commented Mar 5, 2020 at 8:05
  • I'm studying in online university. This is how they teaching us. Commented Mar 5, 2020 at 8:38
  • Remove class ShopView and move the productList property to the ShopViewController class (also extension code) then if we assume that ShopViewControllerModel is the Model class of MVC then rename it to ShopViewModel and make it not inherit from UIViewController (and remove irrelevant functions). Then you have a good MVC design that most of us are very familiar with. Commented Mar 5, 2020 at 8:56
  • but if I delete ShopView class , how this should be MVC, where is View? In ShopView, I can place settings and constraints to the tableview. If I will remove it, I will get MC paradigm, with controller which will be hold all views? Am I right or not? Commented Mar 5, 2020 at 9:13

2 Answers 2

1

As already mentioned in comments to original post, the DataSource for your TableView should be your ViewController class.

You can extract fetching products and providing data to separate ShopModel class, but it should not inherit UIViewController.

Try something like this:

import UIKit

struct Product: Codable {
    var title: String
    var price: Double
    var isFeatured: Bool
}

class ShopViewController: UIViewController {

    private var products = [Product]()
    private var tableView: UITableView!

    override func viewDidLoad() {
        setupLayout()
        fetchProducts()
    }

    private func setupLayout() {
        tableView = UITableView()
        view.addSubview(tableView)

        tableView.translatesAutoresizingMaskIntoConstraints = false
        tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
        tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
        tableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
        tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true

        tableView.dataSource = self

        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "productCell")
    }

    private func fetchProducts() {
        URLSession.shared.dataTask(with: URL(string: "192.168.1.1/products")!, completionHandler: { data, response, error in
            guard error == nil else {
                print(error!.localizedDescription)
                return
            }

            do {
                self.products = try JSONDecoder().decode([Product].self, from: data!)
                // Refresh table after downloading products and make sure to do it from main thread
                DispatchQueue.main.async {
                    self.tableView.reloadData()
                }
            } catch {
                print("Error decoding list of products from received data.")
            }
        }).resume()
    }
}

extension ShopViewController: UITableViewDataSource {

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if section == 0 {
            // Featured products
            return products.filter { $0.isFeatured }.count
        } else {
            // All products
            return products.count
        }
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "productCell", for: indexPath)
        cell.textLabel?.text = products[indexPath.row].title
        cell.detailTextLabel?.text = String(products[indexPath.row].price)
        return cell
    }
}

I've simplified Product model a bit and fetch it from placeholder URL, but I hope you get the point.

Sign up to request clarification or add additional context in comments.

6 Comments

I just answered in comments: but if I delete ShopView class , how this should be MVC, where is View? In ShopView, I can place settings and constraints to the tableview. If I will remove it, I will get MC paradigm, with controller which will be hold all views? Am I right or not?
Your Model is Product here, View - UITableView, Controller - ShopViewController.
WOW, this is the greatest news that I've understand right now! THanks a lot!
@СергейУдалов just to remind you your view code and controller code are in Controller so this is not MVC pattern. Your view code should not be in Controller.
@HabinLama That depends on what is considered "View code". Controller here "controls" the dumb view and tells it what data to display and where to be in view hierarchy. And it's what it should do in MVC. View (UITableView in our case) knows how to draw itself, how to handle sections, updates, etc, but knows nothing else about model or it's surroundings, which is also fine for MVC.
|
0
import UIKit

struct Product: Codable { // Model
    var title: String
    var price: Double
    var isFeatured: Bool
}

class ShopView: UIView { // View

    let shopTableView: UITableView = {
        let tableView = UITableView()
        tableView.backgroundColor = .white
        tableView.translatesAutoresizingMaskIntoConstraints = false
        return tableView
    }()

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupView()
    }

    private func setupView() {
        addSubview(shopTableView)
        shopTableView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
        shopTableView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
        shopTableView.topAnchor.constraint(equalTo: topAnchor).isActive = true
        shopTableView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
    }

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

class ShopViewController: UIViewController { //  Controller

    private var products = [Product]()
    let shopView = ShopView()
    let cellId = "cellId"

    override func viewDidLoad() {
        super.viewDidLoad()
        setupDelegatesAndDataSources()
        fetchProducts()
    }

    private func setupDelegatesAndDataSources() {
        shopView.shopTableView.register(UITableViewCell.self, forCellReuseIdentifier: cellId)
        shopView.shopTableView.dataSource = self
    }

    private func fetchProducts() {
        let data1 = Product(title: "Hello", price: 123.0, isFeatured: true)
        let data2 = Product(title: "Hey", price: 12345.00, isFeatured: true)
        let data3 = Product(title: "Hi", price: 1234567.00, isFeatured: true)
        let data4 = Product(title: "Bye", price: 123456789.000, isFeatured: true)
        products = [data1, data2, data3, data4]
    }

//    private func fetchProducts() {
//        URLSession.shared.dataTask(with: URL(string: "192.168.1.1/products")!, completionHandler: { data, response, error in
//            guard error == nil else {
//                print(error!.localizedDescription)
//                return
//            }
//
//            do {
//                self.products = try JSONDecoder().decode([Product].self, from: data!)
//                // Refresh table after downloading products and make sure to do it from main thread
//                DispatchQueue.main.async {
//                    self.shopView.shopTableView.reloadData()
//                }
//            } catch {
//                print("Error decoding list of products from received data.")
//            }
//        }).resume()
//    }

    override func loadView() {
        view = shopView
    }
}

extension ShopViewController: UITableViewDataSource {

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if section == 0 {
            // Featured products
            return products.filter { $0.isFeatured }.count
        } else {
            // All products
            return products.count
        }
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = shopView.shopTableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath)
        cell.textLabel?.text = products[indexPath.row].title
        cell.detailTextLabel?.text = String(products[indexPath.row].price)
        return cell
    }
}

Just an updated answer of Lieksu.

Model = Product

View = ShopView

Controller = ShopViewController

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.