Project 3 — Views and Modifiers
Это технический проект без одного большого приложения. Мы разберём правила, которые уже использовали в WeSplit и Guess the Flag, и научимся собирать небольшие View в понятные компоненты.
Готовность к проекту
0 из 4 рекомендуемых тем изучено. Проект всегда доступен — это не ограничение.
Рабочий vertical slice
import SwiftUI
struct PrimaryButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration.label
.font(.headline)
.frame(maxWidth: .infinity)
.padding()
.background(configuration.isPressed ? .indigo.opacity(0.7) : .indigo)
.foregroundStyle(.white)
.clipShape(.rect(cornerRadius: 12))
.scaleEffect(configuration.isPressed ? 0.98 : 1)
}
}
struct StatCard: View {
let title: String
let value: Int
var body: some View {
VStack(alignment: .leading) {
Text(title)
.foregroundStyle(.secondary)
Text(value, format: .number)
.font(.largeTitle.bold())
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
.background(.thinMaterial)
.clipShape(.rect(cornerRadius: 16))
}
}
struct ContentView: View {
@State private var count = 0
var body: some View {
VStack(spacing: 20) {
StatCard(title: "Нажатий", value: count)
Button("Увеличить") { count += 1 }
.buttonStyle(PrimaryButtonStyle())
}
.padding()
}
}
#Preview { ContentView() }Здесь StatCard отвечает за composition, а ButtonStyle — за поведение и pressed state кнопки. Это две разные задачи, поэтому их не стоит смешивать в одном modifier.
Почему View — struct
SwiftUI View описывает желаемый интерфейс, а не хранит живой экран. Небольшой value type легко создать заново, сравнить и объединить с другими View.
struct CapsuleText: View {
let text: String
var body: some View {
Text(text)
.font(.largeTitle)
.padding()
.foregroundStyle(.white)
.background(.blue)
.clipShape(.capsule)
}
}Знакомые struct, property и protocol соответствие образуют новый reusable View.
Modifiers создают новые значения
Text("Hello").padding() не меняет исходный Text. Modifier возвращает новый View, который оборачивает предыдущий. Поэтому порядок важен: фон до padding и фон после padding покрывают разную область.
Text("Hello")
.background(.red)
.padding()
Text("Hello")
.padding()
.background(.red)Composition вместо наследования
Большой body дели на computed properties и отдельные structs. Для настройки передавай данные через initializer — тот самый memberwise initializer из Day 10.
struct Title: View {
let text: String
var body: some View { Text(text).font(.headline) }
}Custom ViewModifier
struct ProminentTitle: ViewModifier {
func body(content: Content) -> some View {
content
.font(.largeTitle.bold())
.foregroundStyle(.blue)
}
}
extension View {
func prominentTitle() -> some View {
modifier(ProminentTitle())
}
}Здесь protocol и extension из Day 13 превращаются в SwiftUI API с естественным синтаксисом.
Глубже: generic containers и @ViewBuilder
Позже generic-параметр вида Card<Content: View> позволит карточке принимать произвольное содержимое, а @ViewBuilder — собирать несколько View из closure. До Day 18 этот синтаксис можно не разбирать: для текущей задачи конкретный StatCard проще, яснее и полностью достаточен.
Испытание
Задание
Создай modifier для крупной синей кнопки с белым текстом, padding и capsule shape. Примени его к двум разным `Button`.Что уже нужно знать
Structs, protocols, extensions и порядок modifiers.Показать подсказку 1
Показать подсказку 2
Показать решение
struct PrimaryButton: ViewModifier {
func body(content: Content) -> some View {
content.padding().frame(maxWidth: .infinity)
.foregroundStyle(.white).background(.blue).clipShape(.capsule)
}
}Что ты сейчас использовал
Swift Знакомые основы
SwiftUI Новое в проекте
- + View
- + some View
- + View modifiers
- + View composition
Теперь базовый набор SwiftUI собран. Закрепи его в Milestone Projects 1–3 →.
Комментарии
Вопросы, находки и подсказки по этой странице.
Войди, чтобы задать вопрос или помочь другим.