Властивість body є центральним елементом протоколу View у SwiftUI, який визначає, який вміст відображається на екрані. Згідно з Apple Developer Documentation, 2024, body є єдиною обов'язковою вимогою протоколу View і повертає деякий тип, що відповідає цьому ж протоколу. SwiftUI викликає body при кожній зміні стану для побудови та порівняння нового дерева елементів.
Головне
body — це обчислювана властивість, яка є єдиною обов'язковою вимогою протоколу View. Кожна структура, що відповідає View, повинна реалізувати body. Властивість повертає вміст, який SwiftUI відображає на екрані — це може бути текст, зображення, кнопка, контейнер із вкладеними елементами або будь-який інший тип, що відповідає протоколу View.
Сигнатура body завжди фіксована: var body: some View { get }. Тип повернення — some View (непрозорий тип), а не конкретний тип. Це означає, що різні Views можуть повертати різні конкретні типи в body, але компілятор Swift фіксує конкретний тип для кожної реалізації на етапі компіляції.
За даними WWDC 2022, body — це точка входу в декларативний опис інтерфейсу. На відміну від UIKit, де ви імперативно створюєте та налаштовуєте UIView, у SwiftUI ви декларативно описуєте, що має бути відображено, а SwiftUI сам обчислює, як це реалізувати.
body повинен поводитися як чиста функція — при однакових вхідних даних (властивості структури та стан) він повинен повертати однакове дерево View. Якщо body залежить від зовнішнього змінюваного стану (глобальних змінних, UserDefaults без обгортки @AppStorage), поведінка стає непередбачуваною, і SwiftUI може перемальовувати екран некоректно.
Обчислювана властивість body не зберігає значення — вона обчислюється щоразу при зверненні. Коли SwiftUI визначає, що стан змінився, він перестворює структуру View і читає нове значення body, щоб отримати актуальне дерево елементів для відображення.
struct CounterView: View {
@State private var count = 0
var body: some View {
VStack {
Text("Лічильник: \(count)")
.font(.largeTitle)
Button("Збільшити") {
count += 1
}
.padding()
.background(.blue)
.foregroundColor(.white)
.cornerRadius(8)
}
}
}
У цьому прикладі body повертає VStack, що містить Text і кнопку з модифікаторами. При натисканні кнопки властивість @State count збільшується, SwiftUI перестворює структуру CounterView і знову викликає body для отримання оновленого дерева з новим значенням Text.
Модифікатори (.font, .padding, .background, .foregroundColor, .cornerRadius) не змінюють оригінальний View, а обгортають його в ModifiedContent — новий тип, що додає модифікацію. Кожен модифікатор створює ще один рівень вкладеності, що важливо враховувати для продуктивності.
some View у типі повернення body — це не просто угода, а обов'язкова вимога компілятора. Swift вимагає, щоб усі шляхи повернення в body мали однаковий конкретний тип. Без @ViewBuilder ви не можете повернути Text в одній гілці та Button в іншій — компілятор видасть помилку.
struct ConditionalView: View {
var isReady: Bool
@ViewBuilder
var body: some View {
if isReady {
Text("Готово")
.foregroundColor(.green)
} else {
ProgressView()
}
}
}
@ViewBuilder на body дозволяє використовувати умовну логіку (if/else, switch) без помилок компіляції. ViewBuilder автоматично обгортає різні гілки в ConditionalContent — спеціальний тип, що приховує відмінності конкретних типів. Це ключова можливість для побудови динамічних інтерфейсів.
Без @ViewBuilder компілятор намагається вивести єдиний тип для всіх шляхів повернення. Якщо типи різні — виникає помилка. Саме тому SwiftUI неявно застосовує @ViewBuilder до body в деклараціях View, хоча в коді користувача анотацію потрібно ставити явно для кастомних методів і властивостей, що повертають кілька Views.
Використання some View замість конкретного типу не знижує продуктивність — компілятор на етапі компіляції знає точний тип і генерує прямий код без динамічної диспетчеризації. AnyView, навпаки, використовує стирання типу (type erasure) з накладними витратами на упаковку в екзистенційний контейнер.
body викликається SwiftUI у трьох основних сценаріях: при першому відображенні View, при зміні @State/@Binding/@ObservedObject/@StateObject та при зміні батьківського View, що передає нові значення через ініціалізатор. SwiftUI також може викликати body при зміні значень середовища (@Environment).
Частота виклику body не повинна вас турбувати — SwiftUI оптимізує перемальовування через механізм ідентичності. Кожен View в ієрархії має унікальний ідентифікатор. Якщо ідентичність і вхідні дані не змінилися — body не викликається, навіть якщо батьківський View перемалювався. Це досягається через порівняння Equatable та стабільність структур.
struct ParentView: View {
var body: some View {
ChildView(name: "Alice") // Стабільна ідентичність
}
}
struct ChildView: View {
let name: String
var body: some View {
Text("Привіт, \(name)!")
}
}
У цьому прикладі, якщо ParentView перемальовується, але передає те саме значення name — ChildView.body не викликається. SwiftUI порівнює вхідні дані структури і, якщо вони не змінилися, пропускає перемальовування дочірнього компонента. Це механізм розрізнення представлень.
Існує кілька пасток, що призводять до несподіваного виклику body: використання класів без ObservableObject, передача замикань, створених всередині body (кожне створення замикання дає нову ідентичність), і неправильне використання EquatableView. Якщо body викликається надто часто — перевірте стабільність ідентичності всіх дочірніх компонентів.
Перше правило: body має бути мінімальним. Виносьте складну логіку в окремі обчислювані властивості або методи, що повертають View. Це покращує читабельність і дозволяє SwiftUI точніше визначати, які частини ієрархії змінилися. Розбивайте великі body на підкомпоненти з чіткими межами відповідальності.
Друге правило: не використовуйте body для виконання роботи. Завантаження даних, робота з мережею, запис у базу даних — все це має відбуватися поза body, у завданнях (tasks), модифікаторах onChange або через ObservableObject. body призначений лише для декларації інтерфейсу.
Третє правило: використовуйте властивість EquatableView або кастомний протокол Equatable для View, якщо стандартного порівняння структур недостатньо. Це дозволяє явно вказати SwiftUI, коли дочірній View потребує перемальовування, і уникнути зайвих викликів body.
Четверте правило: якщо body містить складні обчислення (форматування, фільтрація, сортування) — використовуйте @State для кешування результату або виносьте обчислення в окремий метод, що викликається з onChange. Повторні обчислення в body при кожному оновленні стану — часта причина гальмування анімації.
П'яте правило: для списків (List, ForEach) забезпечуйте стабільні ідентифікатори через параметр id. Без стабільної ідентичності ForEach перестворює всі елементи при будь-якій зміні, викликаючи body для кожного з них, навіть якщо змінився лише один елемент.
Часто задавані питання
body — обчислювана властивість протоколу View, що повертає вміст для відображення. Це єдина обов'язкова вимога протоколу. Тип повернення — some View, що дозволяє SwiftUI оптимізувати ієрархію на етапі компіляції.
Так, SwiftUI викликає body при кожній зміні стану (@State, @Binding, @ObservedObject) або вхідних даних. Це нормальна поведінка декларативного фреймворку. SwiftUI оптимізує частоту викликів через механізм ідентичності та Equatable-порівняння.
some View — непрозорий тип, що дозволяє приховати конкретну реалізацію. Компілятор фіксує тип на етапі компіляції, забезпечуючи продуктивність прямого виклику. Це дає гнучкість: можна змінити тип повернення без зміни сигнатури.
Ні, body не може бути опціональним — тип повернення some View не допускає nil. Якщо потрібно приховати елемент за умовою, використовуйте умовну логіку всередині @ViewBuilder або повертайте EmptyView, який не займає місця в ієрархії.
Кожен модифікатор створює новий шар ModifiedContent, збільшуючи глибину ієрархії. Для більшості екранів (до 50 модифікаторів) вплив непомітний. Надмірна кількість модифікаторів (сотні) може сповільнити diffing. Групуйте пов'язані модифікатори в кастомні розширення.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також