Когда я занимался разработкой своего последнего проекта, я столкнулся с задачей создания удобного и интуитивно понятного интерфейса для отображения и переключения данных с физических датчиков по анализу качества окружающей среды. В этой статье я использую в качестве примеров параметры «Качество воздуха» и «УФ-индекс». Я не буду вдаваться в подробности работы с MapKit и маркерами, но пишу об этом для введения в контекст.

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

Мне был нужен аналог контекстного меню, но ТЗ требовало, чтобы появление опций появлялось не в отдельном нативном вью. Это должно было работать так: при нажатии на кнопку она разворачивается вниз, предлагая выбрать параметр или нажать на инфокнопку "What is this".

В этой статье я расскажу о том, как я создал компонентный вью с помощью UIKit в коде без использования Сториборда. Вот какие компоненты я использовал:

Переменные:

  • Константы для размеров: Здесь мы определяем размеры, которые будем использовать для размещения и стилизации наших элементов интерфейса.

  • Основная и второстепенная UIStackView: Эти элементы используются для группировки и вертикального размещения кнопок. Стеки было удобно использовать, потому что при параметре .isHidden = true у кнопок (кнопки скрыты) StackView автоматически сворачивается.

  • Кнопки: Здесь мы создаём основную кнопку, которая отображает текущий параметр, а также отвечает за активацию меню, кнопку для отображения качества воздуха, кнопку для отображения УФ-индекса, информационную кнопку и круглую кнопку для отображения пиктограммы текущего параметра. Сейчас она не отрабатывает событий.

Функции:

  • setupViews(): Эта функция отвечает за добавление всех элементов интерфейса на главное представление и их базовую настройку.

  • setupConstraints(): Здесь мы устанавливаем ограничения для всех элементов интерфейса, определяя их положение и размеры относительно друг друга и главного представления.

  • createButton(...): Это универсальная функция для создания кнопок с различными свойствами, что позволяет избежать дублирования кода при создании каждой новой кнопки.

Отработка событий:

  • Нажатие на основную кнопку: Функция mainButtonTapped() вызывается, когда пользователь нажимает на основную кнопку, что приводит к разворачиванию или сворачиванию меню.

  • Нажатие на кнопку качества воздуха: Функция airQualityButtonTapped() вызывается, когда пользователь выбирает параметр "Качество воздуха".

  • Нажатие на кнопку УФ-индекса: Функция uvIndexButtonTapped() активируется при выборе параметра "УФ-индекс".

Вот код этого элемента с моими комментариями:

import UIKit import SnapKit

// Перечисление для определения текущего параметра enum Parameter { case airQuality case uvIndex }

// Основной класс представления датчика class SensorView: UIView {
// Структура для определения размеров и констант макета
struct Layout {
    static let screenWidth = UIScreen.main.bounds.width
}

// Текущий выбранный параметр
var currentParameter: Parameter = .airQuality

// Основной вертикальный stack view
let primaryStackView: UIStackView = {
    let stack = UIStackView()
    stack.backgroundColor = .white
    stack.axis = .vertical
    stack.layer.cornerRadius = 26
    let insets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 15)
    stack.layoutMargins = insets
    stack.isLayoutMarginsRelativeArrangement = true
    
    return stack
}()

// Второстепенный вертикальный stack view
let secondaryStackView: UIStackView = {
    let stack = UIStackView()
    stack.backgroundColor = .white
    stack.axis = .vertical
    let insets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
    stack.layoutMargins = insets
    stack.isLayoutMarginsRelativeArrangement = true
    return stack
}()

// Главная кнопка, показывающая текущий параметр
private lazy var mainButton: UIButton = {
    let button: UIButton = createButton(title: "Air Quality".uppercased(), action: #selector(mainButtonTapped), height: 58, target: self)
    button.setImage(UIImage.init(systemName: "chevron.down"), for: .normal)
    button.contentHorizontalAlignment = .left
    button.imageEdgeInsets = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: -10)
    button.titleEdgeInsets = UIEdgeInsets(top: 0, left: 40, bottom: 0, right: 0)
    return button
}()

// Кнопка для выбора качества воздуха
private lazy var airQualityButton: UIButton = {
    let button = createButton(title: "Air Quality", action: #selector(airQualityButtonTapped), height: 45, target: self)
    button.isHidden = true
    button.contentHorizontalAlignment = .leading
    button.setImage(UIImage.init(systemName: "wind"), for: .normal)
    button.contentEdgeInsets = UIEdgeInsets(top: 0, left: 50, bottom: 0, right: 0)
    button.imageEdgeInsets = UIEdgeInsets(top: 0, left: -10, bottom: 0, right: 0)
    return button
}()

// Кнопка для выбора индекса ультрафиолетового излучения
private lazy var uvIndexButton: UIButton = {
    let button = createButton(title: "UV index", action: #selector(uvIndexButtonTapped), height: 45, target: self)
    button.isHidden = true
    button.contentHorizontalAlignment = .leading
    button.setImage(UIImage.init(systemName: "sun.max.fill"), for: .normal)
    button.contentEdgeInsets = UIEdgeInsets(top: 0, left: 50, bottom: 0, right: 0)
    button.imageEdgeInsets = UIEdgeInsets(top: 0, left: -10, bottom: 0, right: 0)
    return button
}()

// Кнопка для вывода дополнительной информации
private lazy var infoButton: UIButton = {
    let button = createButton(title: "What is this?", action: #selector(mainButtonTapped), height: 58, target: self)
    button.isHidden = true
    
    if let currentTitle = button.title(for: .normal) {
        let attributes: [NSAttributedString.Key: Any] = [
            .underlineStyle: NSUnderlineStyle.single.rawValue
        ]
        
        let attributedTitle = NSAttributedString(string: currentTitle, attributes: attributes)
        button.setAttributedTitle(attributedTitle, for: .normal)
    }
    
    return button
}()

// Круглая кнопка для визуализации текущего параметра
private lazy var roundButton: UIButton = {
    let button = UIButton()
    let roundButtonDiameter: CGFloat = 75
    button.backgroundColor = .cyan
    button.layer.cornerRadius = roundButtonDiameter / 2
    button.widthAnchor.constraint(equalToConstant: roundButtonDiameter).isActive = true
    button.heightAnchor.constraint(equalToConstant: roundButtonDiameter).isActive = true
    button.setImage(UIImage.init(systemName: "wind"), for: .normal)
    return button
}()

// Инициализатор
init() {
    super.init(frame: .zero)
    
    setupViews()
    setupConstraints()
}

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

// Метод для настройки представлений
func setupViews() {
    self.addSubview(primaryStackView)
    primaryStackView.addArrangedSubview(mainButton)
    primaryStackView.addArrangedSubview(secondaryStackView)
    secondaryStackView.addArrangedSubview(airQualityButton)
    secondaryStackView.addArrangedSubview(uvIndexButton)
    primaryStackView.addArrangedSubview(infoButton)
    self.addSubview(roundButton)
    
}

// Метод для настройки ограничений
func setupConstraints() {
    primaryStackView.snp.makeConstraints { make in
        make.top.bottom.right.left.equalToSuperview().inset(30)
    }
    
    roundButton.snp.makeConstraints { make in
        make.centerY.equalTo(mainButton)
        make.right.equalTo(primaryStackView).offset(20)
    }
}

// Метод для создания кнопок, чтобы не копировать код
private func createButton(title: String, action: Selector, height: CGFloat, target: Any) -> UIButton {
    let button = UIButton()
    button.setTitle(title, for: .normal)
    button.setTitleColor(.black, for: .normal)
    button.backgroundColor = .clear
    button.layer.shadowColor = UIColor.black.cgColor
    button.layer.shadowOffset = CGSize(width: 0, height: 2)
    button.layer.shadowOpacity = 0.2
    button.layer.shadowRadius = 4
    button.addTarget(target, action: action, for: .touchUpInside)
    button.widthAnchor.constraint(equalToConstant: Layout.screenWidth * 0.6).isActive = true
    button.heightAnchor.constraint(equalToConstant: height).isActive = true
    return button
}

// Действие при нажатии на главную кнопку
@objc private func mainButtonTapped() {
    let areButtonsHidden = airQualityButton.isHidden
    
    airQualityButton.isHidden.toggle()
    uvIndexButton.isHidden.toggle()
    infoButton.isHidden.toggle()
    
    let image = areButtonsHidden ? UIImage(systemName: "chevron.up") : UIImage(systemName: "chevron.down")
    mainButton.setImage(image, for: .normal)
}

// Действие при нажатии на кнопку качества воздуха
@objc private func airQualityButtonTapped() {
    mainButton.setTitle("Air Quality".uppercased(), for: .normal)
    airQualityButton.isHidden = true
    uvIndexButton.isHidden = true
    infoButton.isHidden = true
    roundButton.setImage(UIImage.init(systemName: "wind"), for: .normal)
    mainButton.setImage(UIImage.init(systemName: "chevron.down"), for: .normal)
}

// Действие при нажатии на кнопку индекса ультрафиолетового излучения
@objc private func uvIndexButtonTapped() {
    mainButton.setTitle("UV index".uppercased(), for: .normal)
    airQualityButton.isHidden = true
    uvIndexButton.isHidden = true
    infoButton.isHidden = true
    roundButton.setImage(UIImage.init(systemName: "sun.max.fill"), for: .normal)
    mainButton.setImage(UIImage.init(systemName: "chevron.down"), for: .normal)
}
}

Комментарии (0)