Разработка
Как я создал собственную дизайн-систему для компонентов iOS-приложения
Если вы создаете свое приложение или работаете в компании, где ваша команда еще не перенесла дизайн компонентов пользовательского интерфейса в отдельную библиотеку, пришло время сделать этот шаг. В этой статье я расскажу вам о своем подходе к созданию многократно используемых компонентов с помощью SwiftUI. Я пропущу базовую настройку библиотеки в Xcode (этому посвящено множество руководств) и сосредоточусь на том, что действительно важно: на создании надежной системы компонентов.
Почему стоит использовать библиотеки компонентов?
Позвольте мне рассказать, почему я такой большой сторонник библиотек компонентов. Во-первых, они значительно снижают нагрузку на ваш основной проект — мы говорим о более быстром времени сборки и чистых кодовых базах. Когда вам понадобится изменить дизайн (а поверьте, вам придется это делать), наличие компонентов, изолированных в отдельном проекте, убережет вас от случайного нарушения бизнес-логики.
Вот основные принципы, которых я придерживался:
- Сохраняйте внешнюю логику для UI — состояния компонентов должны контролироваться внешними переменными. Например, состояние тумблера должно находиться вне компонента, а не внутри него.
- Максимизируйте настраиваемость — я сделал компоненты легко настраиваемыми с помощью переменных, а не сложной внутренней логики. Такой подход сэкономил мне бесчисленное количество часов при адаптации компонентов для различных случаев использования.
- Используйте атомарный дизайн — подумайте о кирпичиках LEGO. Я собираю сложные компоненты из более мелких, независимых частей. Это делает обслуживание и обновление гораздо более удобным.
Начало работы: основа
Прежде чем погрузиться в сложные компоненты, мне нужен прочный фундамент. Давайте начнем с управления цветом:
import SwiftUI
import UIKit
public extension Color {
static var primaryGreen : Color { Color(uiColor: UIColor(red: 0.169, green: 0.38, blue: 0.451, alpha: 1.00)) } // #2b6173
static var darkGrey : Color { Color(uiColor: UIColor(red: 0.36, green: 0.38, blue: 0.4, alpha: 1)) } // #5D6166
static var border : Color { Color(uiColor: UIColor(red: 0.871, green: 0.878, blue: 0.89, alpha: 1)) } // #dee0e3
static var secondaryYellow : Color { Color(uiColor: UIColor(red: 0.91, green: 0.96, blue: 0.41, alpha: 1.00)) } // #E8F569
static var backgroundGrey : Color { Color(uiColor: UIColor(red: 0.98, green: 0.98, blue: 0.98, alpha: 1)) } // #fafafa
static var alertRedOpacity: Color { Color(red: 0.996, green: 0.945, blue: 0.945) } // #FEF2F1
static var alertRed: Color { Color(red: 0.792, green: 0.11, blue: 0.2) } // #CA1C33
static var alertDarkRed: Color { Color(red: 0.345, green: 0, blue: 0.008) } // #580002
static var alertGreenOpacity: Color { Color(red: 0.882, green: 0.992, blue: 0.957) } // #e1fdf4
static var alertDarkGreen: Color { Color(red: 0, green: 0.314, blue: 0.145) } // #005025
static var alertGreen: Color { Color(red: 0.016, green: 0.69, blue: 0.435) } // #04b06f
}
Для более быстрой генерации цветов я использовал этот HEX-конвертер.
В качестве базового элемента я создал компонент EXBase
. Это мой основной компонент, когда мне нужен постоянный фон и отступы для разных элементов:
xxxxxxxxxx
public struct EXBase<Content: View>: View {
let content: () -> Content
public init(@ViewBuilder content: @escaping () -> Content) {
self.content = content
}
public var body: some View {
content()
.padding(12)
.background(Color.backgroundGrey)
.cornerRadius(8)
}
}
Разбор реального примера: EXInfoCard
Давайте рассмотрим нечто практичное — компонент EXInfoCard
. Вы наверняка видели этот паттерн бесчисленное количество раз в мобильных приложениях: информационная карточка с иконкой, заголовком, текстом и иногда кнопкой. Целью здесь была максимальная гибкость при полной независимости от бизнес-логики.
Вот как я его структурировал:
xxxxxxxxxx
public struct EXInfoCard: View {
var title: String
var text: String
var icon: IconType?
var isButton: Bool
var buttonIcon: Image?
var buttonText: String? = nil
var buttonAction: (() -> Void)?
public init(
title: String,
text: String,
icon: IconType? = nil,
isButton: Bool = false,
buttonIcon: Image? = nil,
buttonText: String? = nil,
buttonAction: (() -> Void)? = nil
) {
self.title = title
self.text = text
self.icon = icon
self.isButton = isButton
self.buttonIcon = buttonIcon
self.buttonText = buttonText
self.buttonAction = buttonAction
}
public var body: some View {
EXBase {
VStack {
VStack(alignment: .leading, spacing: 5) {
if let icon = icon {
switch icon {
case .imageName(let imageName) where !imageName.isEmpty:
Text(imageName)
case .image(let image):
image
.foregroundColor(.primaryGreen)
default:
EmptyView()
}
}
Text(title)
.font(.system(.headline, weight: .semibold))
Text(text)
.font(.system(.subheadline, weight: .regular))
.foregroundColor(.darkGrey)
}
.frame(maxWidth: .infinity, alignment: .leading)
if isButton, let buttonIcon = buttonIcon, let buttonText = buttonText, let buttonAction = buttonAction {
Button(action: {
buttonAction()
}) {
HStack {
Text(buttonText)
.font(.system(.subheadline, weight: .semibold))
buttonIcon
}
.frame(maxWidth: .infinity)
}
.buttonStyle(EXPrimaryButtonStyle(showLoader: .constant(false)))
.padding(.top, 5)
}
}
}
}
}
Обратите внимание, что компонент не делает никаких предположений о том, когда показывать или скрывать элементы. Вместо этого он реагирует на внешние переменные состояния. Например, видимость кнопки контролируется параметром isButton
, который вы можете привязать к любой @State
переменной в ваших представлениях:
xxxxxxxxxx
@State private var condition: Bool = false
EXInfoCard(
title: "Earn Rewards",
text: "Learn how our points system works",
icon: .imageName("⭐"),
isButton: condition,
buttonIcon: Image(systemName: "arrow.right"),
buttonText: "Learn More",
buttonAction: {}
)
// Condition logic definition
Основные выводы
Создание этой библиотеки преподало мне ценные уроки по созданию действительно многократно используемых компонентов пользовательского интерфейса:
- Думайте системами, а не экранами — Каждый компонент должен работать везде, а не только в вашем текущем сценарии использования.
- Делайте конфигурации общедоступными — Использование общедоступных перечислений и структур означает лучшую возможность повторного использования в разных проектах.
- Сохраняйте широкие, но значимые возможности настройки — Соблюдайте баланс между гибкостью и сложностью.
Я сделал эту библиотеку с открытым исходным кодом, так что не стесняйтесь использовать ее в своем следующем проекте или в качестве шаблона для своей собственной системы. Вы можете найти ее на GitHub и ознакомиться с более подробной информацией на сайте библиотеки.
Помните, что хорошая библиотека компонентов развивается вместе с вашими потребностями. Начните с малого, сосредоточьтесь на возможности повторного использования и продолжайте совершенствоваться по мере продвижения. Поверьте, в будущем вы будете благодарны себе за то, что уделили время созданию этой основы.
-
Видео и подкасты для разработчиков3 недели назад
Как устроена мобильная архитектура. Интервью с тех. лидером юнита «Mobile Architecture» из AvitoTech
-
Новости4 недели назад
Видео и подкасты о мобильной разработке 2025.9
-
GitHub4 недели назад
Video Player App — приложение для просмотра видео на MVVM и Kotlin
-
Новости3 недели назад
Видео и подкасты о мобильной разработке 2025.10