Небольшое вступление


Всем 404! Большинство IOS-разработчиков не понимают как работать с Apple-MVC. Из-за чего появляется необоснованная критика, маленькие проекты стараются писать на архитектурах для этого не предназначенных, ViewController'ы становятся огромными, не читаемыми и тд.

В этой статье я покажу простой способ как облегчить жизнь себе, жизнь себе, заглянувшему в проект через 3 месяца, и, конечно, всем ViewController'aм. Для кого он подойдет? Для начинающих разработчиков или разработчиков с небольшими проектами, которые в итоге получатся с чистой и красивой структурой.

Где же все тонут?


Для начала давайте посмотрим на всеми любимое стандартное красивое MVC.

image

А теперь посмотрим на то, что осуществляется большинством.

image

Классно? Нет! Поэтому мы так делать не будем, а реализуем первый вариант.

Делаем структуру красоте


Создаем обычный Single View App. В поле выбора языка я ставлю Swift, больше ничего не включая: ни тестов, ни CoreDat'ы.

Я буду показывать на примере создания витрины магазина, т.к. нам нужно затронуть все части MVC, будем брать данные из model в controller распределять и показывать во view.

Окей, задача есть, пустой проект есть. Теперь нырнем в проект, я распределю все по папкам и создам пустые файлы, чтобы вы понимали где они должны лежать в дальнейшем. Также прикрепил storyboard структуру, чтобы не тратить время в дальнейшем. Не забудьте указать reuse id у cell, я написал ProductCell.

image

Красиво? Конечно красиво. Продолжаем…

Немного магии swift


Сейчас можно и к реализации приступить. Начнем с простого, view. Нам нужно создать все IBOutlets, и настройки нашего представления.

// Начинаем с импорта библиотек, зачастую во view нам будет необходим только UIKit. 
import UIKit

class ShopView: UIView{
    // Подвязываем нашу переменную к объекту в storyboard. 
    @IBOutlet weak var tableView: UITableView!
    // Создаем функции для настройки нашей view. 
    func configure(){
        // Здесь могут быть любые параметры, как установка цвета текста, так и установка высоты строки таблицы...
        tableView.estimatedRowHeight = 50
        tableView.rowHeight = 100
    }
}

Так будет выглядеть наша view, в configure() функции я лишь задаю настройки, но view нужно использовать и для анимаций, и для смены названия title'а кнопок и тд… Так примитивно скажете? Но почему тогда так мало используют это преимущество?! Вот так я например использую анимации в controller'e: view.hideBorders(self) и все, а view уже сделает красивую анимацию.

// Передаем ссылку на view из контроллера. 
func hideBorders(view: UIView){
    UIView.animate(withDuration: 0.3,
                   delay: 0,
                   options: .curveEaseInOut,
                   animations: {
           // И уже внутри анимации меняем высоту при помощи constraint. 
           self.createViewHeighConstraint.constant = view.bounds.height * 0.6
           // Изменяем такие параметры, как округление краев, видимость тени и тд. 
           self.editButton.layer.cornerRadius = 20
           self.postView.layer.shadowOpacity = 0
           self.postView.layer.cornerRadius = 0
           view.layoutIfNeeded()
    }, completion: nil)
}

Огонь! Но вернемся к нашему проекту, время реализовать model. И так у нас будут товары, и пусть будут их группы.

// Хорошим тоном считается, что в model не должно быть никаких данных о View.
// Да, не всегда это получается, но в нашем случае достаточно импортировать Foundation.
import Foundation
// Теперь нам нужно сделать наш продукт, воспользуемся структурой. 
public struct Product {
  public let name: String
}
// Подумаем немного о будущем, у нас могут быть разные продукты, создадим их группы. 
public struct ProductGroup {
  public let products: [Product]
  public let title: String
}
// Магия swift позволяет нам декорировать(расширять) структуры с помощью extension. 
extension ProductGroup{
    // Создадим функцию, позволяющую нам получить массив овощей(структур Product). 
    public static func vegetables() -> ProductGroup{
       // Собираем массив овощей.
        let products = [
            Product(name: "Огурцы"),
            Product(name: "Помидоры"),
            Product(name: "Капуста")
        ]
        // Возвращаем наш массив с названием "Овощи". 
        return ProductGroup(products: products, title: "Овощи")
    }
}

Замечательно, наша модель готова, осталось собрать контроллер, давайте преступим. первым делом нужно будет создать две наших главных сущности модель и вью. Затем я вынесу настройки контроллера в private extension и передам данные из model в нашу таблицу.

// В controller'e у нас могут быть разные библиотеки, но сегодня нам потребуется только UIKit.
import UIKit

class ShopController: UIViewController {
    // Создаем переменную-модель и получаем в нее овощи.
    private var shopModel = ProductGroup.vegetables()
    
    // Дальше создаем вычисляемое свойство (объяснение длинновато для комментария смотри внизу под кодом)
    private var shopView: ShopView! {
        guard isViewLoaded else { return nil }
        return (view as! ShopView)
    }
    // Помните мы делали функцию настройки view, вот здесь и применяем ее чтобы при загрузке controller'a наша view тоже настроилась. 
    override func viewDidLoad() {
        super.viewDidLoad()
        // Тем самым настройка view вообще не занимает места в контроллере.
        configure()
    }
}

private extension ShopController{
    // Но делегаты есть делегаты их присваиваем и оставляем в контроллере.
    func configure(){
        shopView.tableView.delegate = self
        shopView.tableView.dataSource = self
    }
}

// Теперь самое время реализовать методы делегатов. 
extension ShopController: UITableViewDataSource, UITableViewDelegate{

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // Возвращаем кол-во ячеек по кол-ву элементов в модели
        return shopModel.products.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        // Помните я говорил, не забудьте reuse Id установить, так вот он нужен именно здесь.
        let cell = tableView.dequeueReusableCell(withIdentifier: "ProductCell")
        // Создаем магическим образом label в cell и присваиваем ей название продукта. 
        cell?.textLabel?.text = shopModel.products[indexPath.row].name
        return cell!
    }
}

Обещал обьяснить про view, не забыли?) Наша shopView — это вычисляемое свойство. Здесь мы проверяем isViewLoaded, чтобы не вызвать непреднамеренную загрузку view при доступе к этому свойству. Если представление уже загружено, мы принудительно преобразуем его в ShopView.

Вот и все, наше mvc готово! Остается нажать command+R и увидеть три ячейки.

Что мы имеем в итоге?


  • Простая, понятная архитектура
  • Равномерная нагрузка на model, view, controller
  • Легкая переиспользуемость view
  • Легкая расширяемость нашего проекта

Заключение


Очень надеюсь что эта статья поможет новичкам держать свои проекты в чистоте, не теряться в коде, а концентрироваться на задачах. Всем добра и меньше багов ^^