Калькулятор растаможки (часть 1)

Вот уже и дошли руки к написанию данного цикла статей, где я хочу наглядно показать принцип DataSource в действии. Что это такое читайте здесь.

Тематику я выбрал не спроста, так как тема актуальная сейчас в нашей стране, можно что-то сделать да и полезное 🙂 (приложение я создал конечно намного раньше, но времени начать статью не было вовсе)

Данное приложение есть в аппстор, ссылка. Оно платное, и если вам интересно поддержать данный проект, вы можете его приобрести. 🙂 Также вы можете поддержать проект другим способом. В футере сайта вся информация 😉

Приложение содержит два экрана. В первом мы выбираем тип транспортного средства. Второй экран является калькулятором, в который нам нужно внести значения исходя выбранного типа и нажать на кнопку “рассчитать”, в итоге ниже мы будем показывать результат вычислений исходя заведомо известных нам формул расчета. Вроде как ничего сложного здесь нет, единственное условие, это реализация всего этого с помощью таблицы.

Начнем мы с первого и главного экрана нашего приложения.

Первым делом нужно оценить наше представление. Выглядит оно совсем просто. Обычная таблица, где каждая ячейка у нас содержит иконку и текст описывающий тип нашего транспортного средства. То есть таблица, которая содержит одну пере-используемую ячейку.

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

Для начала создадим файл Vehicle.swift. Он будет содержать данные, и опишем в нем типы транспортных средств.

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


//
//  Vehicle.swift

import Foundation
import UIKit

enum VehicleType {
    case car
    case electric
    case hybrid
    case moto
    case truck
    
    func typeDescription() -> String {
        switch self {
        case .car: return "Легковые"
        case .electric: return "Электро"
        case .hybrid: return "Гибрид"
        case .moto: return "Мото"
        case .truck: return "Грузовое"
        }
    }

    func typeImage() -> UIImage? {
        switch self {
        case .car: return Constants.VehicleIcons.car
        case .electric: return Constants.VehicleIcons.electric
        case .hybrid: return Constants.VehicleIcons.hybrid
        case .moto: return Constants.VehicleIcons.moto
        case .truck: return Constants.VehicleIcons.truck
        }
    }
}

Начнем мы конечно же с перечисляемого типа enum в котором и перечислим наши возможные типы, а также создадим две функции, которые буду возвращать нам, исходя заданого типа, описание типа String, и иконку типа UIImage. Для более хорошей структуры кода, мы так же создадим файл с константами, где будем хранить цвета и иконки. Обратите внимание, функция tinted это расширение класса UIImage который окрашивает нашу иконку в заданный цвет. Довольно таки полезная вещь, можете смело использовать в своих проектах.


//
//  Constants.swift

import Foundation
import UIKit

struct Constants {
    static let mainColor = UIColor(red: 59/255.0, green: 41/255.0, blue: 49/255.0, alpha: 1)
    
    struct VehicleIcons {
        static let car = UIImage(named: "icons8-sedan-100")?.tinted(with: Constants.mainColor)
        static let electric = UIImage(named: "icons8-sedan-100")?.tinted(with: Constants.mainColor)
        static let hybrid = UIImage(named: "icons8-fiat-500-100")?.tinted(with: Constants.mainColor)
        static let moto = UIImage(named: "icons8-motorcycle-filled-100-2")?.tinted(with: Constants.mainColor)
        static let truck = UIImage(named: "icons8-truck-100")?.tinted(with: Constants.mainColor)
        
        private init() {}
    }
    
    private init() {}
}

extension UIImage {
    func tinted(with color: UIColor) -> UIImage? {
        defer { UIGraphicsEndImageContext() }
        UIGraphicsBeginImageContextWithOptions(size, false, scale)
        color.set()
        withRenderingMode(.alwaysTemplate).draw(in: CGRect(origin: .zero, size: size))
        return UIGraphicsGetImageFromCurrentImageContext()
    }
}

В этом моменте можно немного похоливарничать на тему enum vs struct для статических переменных, но мне в голове не укладывается как можно использовать перечисляемый тип для статических переменных. Для избежания инициализации структуры мы просто блокируем возможность использования главного инициализатора. Если уж так вам это принципиально. 🙂 Смысл в том что для перечисляемого типа иногда нужно использовать статические переменные и язык нам не запрещает этого делать, но уместно использовать их по предназначению, но не использовать это как главный механизм реализации статических данных. ИМХО 🙂

Далее можем переходить смело в наш Storyboard. Здесь все просто, реализуем наш стартовый контролер через UINavigationController в который подсоединим наш главный табличный контроллер SelectVehicleTableViewController.

Main.storyboard

В табличном контроллере добавим одну ячейку, в которою добавим UIImageView и UILabel, а также добавим констрейны. Назначим идентификатором ячейки ключевое слово VehicleCell.

SelectVehicleTableViewController

Создадим новый класс ячейки, в который с помощью IBOutlet сделаем связи между нашими объектами вывода.


//
//  VehicleCell.swift
import UIKit

class VehicleCell: UITableViewCell {
    static let identifier = "VehicleCell"
    @IBOutlet weak var nameLabel: UILabel!
    @IBOutlet weak var iconImageView: UIImageView!
    
}

Думаю с этим ничего сложного.

Теперь переходим к табличному контролеру где будем реализовывать нашу логику.


//
//  SelectVehicleTableViewController.swift

import UIKit

class SelectVehicleTableViewController: UITableViewController {

    let dataSource: [VehicleType] = [.car, .electric, .hybrid, .moto, .truck]
    
    override func viewDidLoad() {
        super.viewDidLoad()

        tableView.tableFooterView = UIView(frame: .zero)
        title = "Тип транспорта"
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return dataSource.count
    }
    
     override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 64
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        if let cell = tableView.dequeueReusableCell(withIdentifier: VehicleCell.identifier, for: indexPath) as? VehicleCell {
            cell.nameLabel.text = dataSource[indexPath.row].typeDescription()
            cell.iconImageView.image = dataSource[indexPath.row].typeImage()
            return cell
        }
        return UITableViewCell()
    }

}

Вот мы и пришли к месту, где впервые встречаемся с dataSource моделью. Принцип в том, что эта модель описывает каким образом нам нужно отрендерить нашу таблицу. Позже этот массив данных можно вообще вынести в константы, и при добавлении новых элементов, вообще не заходить в контроллер, так как нам ничего не нужно в нем менять, потому что он работает сугубо с dataSource моделью. Но об этом уже мы будем говорить в следующих статьях.

Продолжение следует… 🙂

P.S: Если у вас возникают вопросы, не ленитесь задавать их в комментариях.

Поделиться

Оставить комментарий