Основное руководство по SwiftUI: многократно используемый пользовательский интерфейс с пользовательскими модификаторами
29 апреля 2023 г.Возможность создавать настраиваемые модификаторы представления — это мощная функция SwiftUI. В этой статье мы рассмотрим примеры того, как эту функцию можно использовать для значительного упрощения создания пользовательского интерфейса. Если вы не знакомы с ViewModifiers в SwiftUI и с тем, как создавать собственные, вы можете прочитать о них здесь
Цель этой статьи — рассказать о различных способах создания пользовательских модификаторов и стилей в SwiftUI и о том, как их можно использовать, чтобы сделать создание пользовательского интерфейса более декларативным, но при этом получить чистый и согласованный конечный результат.
Последний пользовательский интерфейс, который мы хотим создать, выглядит следующим образом:
Давайте рассмотрим все отдельные компоненты на экране:
- Изображение: стандартный компонент изображения с некоторым радиусом угла.
- Тексты: у нас есть заголовок и основной текст.
- Кнопка: полноразмерная кнопка.
Простой код SwiftUI
Если построить этот экран без каких-либо модификаторов, код будет выглядеть примерно так:
struct ContentView: View {
var body: some View {
VStack (alignment: .leading) {
Image("feature")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(minWidth: 0, maxWidth: .infinity)
.frame(height: 220)
.cornerRadius(12)
.padding(.bottom, 12)
Text("Custom ViewModifiers in SwiftUI are the best!")
.foregroundColor(Color("titleTextColor"))
.font(.system(size: 20, weight: .bold))
.padding(.bottom, 12)
Text("Custom ViewModifiers in SwiftUI let you create resuable styles that can be applied to all your views")
.foregroundColor(Color("bodyTextColor"))
.font(.system(size: 14, weight: .medium))
Spacer()
Button(action: {
}) {
Text("Label")
.font(.system(size: 14, weight: .medium))
}
.frame(minWidth: 0, maxWidth: .infinity)
.padding(.horizontal, 10)
.padding(.vertical, 12)
.background(Color.blue)
.foregroundColor(Color.white)
.cornerRadius(12)
}
.padding(.all, 16)
}
}
С этим подходом связано несколько проблем:
- Стили для некоторых элементов (например, для текста заголовка и подробностей) придется дублировать.
- Изменения в некоторых общих стилях (отступы элементов, радиус углов и т. д.) необходимо будет вносить в нескольких местах.
Теперь вы можете решить эту проблему способом UIKit, создав пользовательские представления, но я не сторонник этого подхода, потому что он предполагает отказ от встроенных представлений и делает адаптацию новых членов команды более сложной. Проще было бы определить некоторые универсальные модификаторы представления, которые можно было бы применять вместо самих стилей.
Давайте разберем общие стили, которые нам нужны:
- Контейнер экрана: сам экран имеет универсальные отступы, это необязательно, но я предпочитаю, чтобы все экраны имели универсальный стиль.
- Радиус угла
- Заголовок и основной текст
- Полная ширина. Элементы должны иметь возможность заполнять ширину своего родителя (кнопки и изображения в приведенном выше примере).
- Стили кнопок
- Масштабирование изображения
Пользовательские модификаторы просмотра
Начнем с углового радиуса:
struct CommonCornerRadius: ViewModifier {
func body(content: Content) -> some View {
content
.cornerRadius(12)
}
}
Этот довольно простой, он позволяет нам применять универсальный радиус угла для элементов. Это упрощает глобальное изменение стилей приложений без необходимости создавать собственные представления или вносить несколько изменений в кодовую базу.
struct FullWidthModifier: ViewModifier {
func body(content: Content) -> some View {
content
.frame(minWidth: 0, maxWidth: .infinity)
}
}
Это упрощает реализацию представлений полной ширины, больше не нужно добавлять .frame
вручную!
struct TitleTextModifier: ViewModifier {
func body(content: Content) -> some View {
content
.foregroundColor(Color("titleTextColor"))
.font(.system(size: 20, weight: .bold))
}
}
struct BodyTextModifier: ViewModifier {
func body(content: Content) -> some View {
content
.foregroundColor(Color("bodyTextColor"))
.font(.system(size: 14, weight: .medium))
}
}
Это позволит использовать общий стиль текста. Обычно вы либо создаете собственные текстовые компоненты, либо служебные функции и добавляете компоненты пользовательского интерфейса с помощью кода.
extension Image {
func aspectFill() -> some View {
self
.resizable()
.aspectRatio(contentMode: .fill)
}
}
Хорошо, вы меня поняли… это не пользовательский модификатор представления, а простое расширение. Это связано с тем, что ViewModifiers совместим с общими представлениями, а некоторые функции, такие как resizable
, применяются только к изображениям, использование комбинации расширений и пользовательских модификаторов помогает обойти это.
struct FullWidthButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration.label
.fullWidth()
.foregroundColor(Color.white)
.font(.system(size: 14, weight: .medium))
.padding(.horizontal, 10)
.padding(.vertical, 12)
.background(configuration.isPressed ? Color.blue.opacity(0.2) : Color.blue)
}
}
struct FullWidthButton: ViewModifier {
func body(content: Content) -> some View {
content
.buttonStyle(FullWidthButtonStyle())
}
}
Наконец, это для кнопки, обратите внимание, что, хотя мы могли бы просто создать ViewModifier для достижения того же эффекта, внешний вид кнопки не изменился бы при нажатии. Это связано с тем, что установка .background
для кнопки заставляет ее использовать этот фон как в нажатом, так и в ненажатом состоянии. ButtonStyle
позволяет нам изменять прозрачность кнопки в зависимости от того, нажата она или нет.
Теперь для удобства мне нравится создавать расширения, использующие эти модификаторы:
extension View {
func commonCornerRadius() -> some View {
modifier(CommonCornerRadius())
}
func fullWidth() -> some View {
modifier(FullWidthModifier())
}
func title() -> some View {
modifier(TitleTextModifier())
}
func body() -> some View {
modifier(BodyTextModifier())
}
func fullWidthButton() -> some View {
modifier(FullWidthButton())
}
}
extension Image {
func aspectFill() -> some View {
self
.resizable()
.aspectRatio(contentMode: .fill)
}
}
Теперь давайте преобразуем код, чтобы использовать их вместо прямого стиля:
struct ContentView: View {
var body: some View {
VStack (alignment: .leading) {
Image("feature")
.aspectFill()
.fullWidth()
.frame(height: 220)
.commonCornerRadius()
.padding(.bottom, 12)
Text("Custom ViewModifiers in SwiftUI are the best!")
.title()
.padding(.bottom, 12)
Text("Custom ViewModifiers in SwiftUI let you create resuable styles that can be applied to all your views")
.body()
Spacer()
Button(action: {
}) {
Text("Awesome")
}
.fullWidthButton()
.commonCornerRadius()
}
.padding(.all, 16)
}
}
Гораздо чище! На первый взгляд кажется, что это требует больше кода и усилий, чем просто установка стилей вручную, но в долгосрочной перспективе это сэкономит много усилий. Лично этот подход также способствует тому, чтобы стиль вашего приложения был более последовательным, поскольку он больше полагался на общие модификаторы, чем на стиль, основанный на представлении за представлением.
И это все! Надеюсь, это поможет вам быстрее и проще создавать свои приложения. Еще одним преимуществом является то, что эти модификаторы можно добавить в любое из ваших приложений и настроить в соответствии с его рекомендациями по стилю. Я также работал над библиотекой, чтобы продвинуть это еще дальше, вы можете проверить ее здесь (PS: во время написав это, библиотека находится на очень ранней стадии, репо пусто: p, но следите за обновлениями)
Оригинал