ویژگی body — عنصر مرکزی پروتکل View در SwiftUI است که تعیین میکند چه محتوایی روی صفحه نمایش داده شود. به گزارش Apple Developer Documentation, 2024، body تنها نیاز اجباری پروتکل View است و نوعی را برمیگرداند که با همین پروتکل سازگار است. SwiftUI body را در هر تغییر وضعیت برای ساخت و مقایسه درخت جدید عناصر فراخوانی میکند.
نکات اصلی
body — یک ویژگی محاسبهشونده (computed property) است که تنها نیاز اجباری پروتکل View میباشد. هر ساختاری که با View سازگار است باید body را پیادهسازی کند. این ویژگی محتوایی را که SwiftUI روی صفحه نمایش میدهد برمیگرداند — این میتواند متن، تصویر، دکمه، کانتینر با عناصر تو در تو یا هر نوع دیگری باشد که با پروتکل View سازگار است.
امضای body همیشه ثابت است: var body: some View { get }. نوع بازگشتی — some View (نوع شفافنما)، نه یک نوع مشخص. این بدان معناست که Viewهای مختلف میتوانند انواع مشخص متفاوتی را در 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 اعمال میکند، هرچند در کد کاربری برای متدها و ویژگیهای سفارشی که چندین View برمیگردانند باید حاشیهنویسی را به صراحت قرار داد.
استفاده از 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 دادههای ورودی ساختار را مقایسه میکند و اگر تغییر نکرده باشند، بازنشانی مؤلفه فرزند را رد میکند. این مکانیزم تفکیک View (view differentiation) است.
چندین دام وجود دارد که منجر به فراخوانی غیرمنتظره body میشود: استفاده از کلاسها بدون ObservableObject، ارسال بستههایی که درون body ایجاد میشوند (هر ایجاد بسته هویت جدیدی میدهد) و استفاده نادرست از EquatableView. اگر body بیش از حد مکرر فراخوانی میشود — پایداری هویت همه مؤلفههای فرزند را بررسی کنید.
قاعده اول: body باید حداقلی باشد. منطق پیچیده را به ویژگیهای محاسبهشونده جداگانه یا متدهایی که View برمیگردانند منتقل کنید. این کار خوانایی را بهبود میبخشد و به SwiftUI اجازه میدهد دقیقتر تعیین کند کدام بخشهای سلسلهمراتب تغییر کردهاند. bodyهای بزرگ را به زیرمؤلفههایی با مرزهای مسئولیت واضح تقسیم کنید.
قاعده دوم: از body برای انجام کار استفاده نکنید. بارگذاری داده، کار با شبکه، نوشتن در پایگاه داده — همه اینها باید خارج از body، در وظایف (task)، تغییردهندههای 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 ایجاد میکند و عمق سلسلهمراتب را افزایش میدهد. برای بیشتر صفحهها (تا ۵۰ تغییردهنده) تأثیر نامحسوس است. تعداد بیش از حد تغییردهندهها (صدها) میتواند diffing را کند کند. تغییردهندههای مرتبط را در افزونههای سفارشی گروهبندی کنید.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید