@ScaledMetric — property wrapper SwiftUI که به طور خودکار مقدار عددی را مطابق با تنظیمات Dynamic Type کاربر مقیاسبندی میکند. مقدار در @ScaledMetric قرار میگیرد و با تغییر اندازه سیستم فونت دوباره محاسبه میشود که دسترسی رابط را برای افراد دارای اختلالات بینایی تضمین میکند. طبق Apple Developer Documentation (2026)، @ScaledMetric از مقیاس UIFontMetrics برای محاسبه مقیاس نسبی بر اساس preferred content size category استفاده میکند. درباره accessibility بیشتر در مطلب accessibility SwiftUI بخوانید.
نکات اصلی
@ScaledMetric — property wrapper SwiftUI که در iOS 14 اضافه شده است و به طور خودکار مقدار عددی (CGFloat, Int, Double) را با اندازه فونت جاری Dynamic Type مقیاسبندی میکند. برخلاف .font(.body) برای فونتها، @ScaledMetric هر پارامتر عددی را مقیاسبندی میکند: padding, spacing, cornerRadius, iconSize — هر چیزی که باید با متن بزرگ به نسبت افزایش یابد.
وظیفه اصلی @ScaledMetric تضمین مقیاسبندی دسترسیپذیری عناصر غیرمتنی رابط است. وقتی کاربر فونت را در تنظیمات iOS بزرگ میکند، دکمهها، آیکونها و فاصلهها باید به نسبت افزایش یابند تا رابط متعادل بماند. @ScaledMetric این وظیفه را به طور خودکار و بدون محاسبه دستی ضریبها حل میکند.
سینتکس پایه @ScaledMetric از مقدار پیشفرض و پارامتر اختیاری relativeTo استفاده میکند. اگر relativeTo مشخص شود، مقیاسبندی به یک سبک متنی خاص (UIFontTextStyle) متصل میشود. اگر مشخص نشود — از مقیاس .body استفاده میشود.
struct AccessibleButton: View {
@ScaledMetric private var padding: CGFloat = 12
@ScaledMetric(relativeTo: .title) private var iconSize: CGFloat = 24
var body: some View {
Label("ارسال", systemImage: "checkmark.circle.fill")
.font(.body)
.padding(padding)
.imageScale(.init(rawValue: iconSize / 24) ?? .medium)
}
}
padding نسبت به .body (پیشفرض) و iconSize نسبت به .title مقیاسبندی میشود. با متن بزرگ، فاصلهها و آیکون به نسبت افزایش مییابند. بدون @ScaledMetric فاصلهها در هر اندازه فونتی 12 pt باقی میماند که منجر به عدم تعادل بصری میشود.
مکانیزم @ScaledMetric بر اساس UIFontMetrics از UIKit است. وقتی SwiftUI نمونهای از @ScaledMetric ایجاد میکند، ضریبی را بر اساس preferred content size category جاری (UIContentSizeCategory) محاسبه میکند. مقدار پایه در scaledValue از UIFontMetrics برای سبک متنی مشخص شده ضرب میشود.
از نظر ریاضی: ScaledMetricValue = baseValue × UIFontMetrics.scaledValue(for: relativeTo). اگر relativeTo مشخص نشود، از UIFontMetrics.default متصل به .body استفاده میشود. با تغییر Dynamic Type، SwiftUI بدنه View را بازسازی میکند، @ScaledMetric scaledValue جدید را محاسبه کرده و UI به طور خودکار از طریق مکانیزم PropertyWrappers شبیه @State بهروز میشود.
مقیاس iOS شامل 11 اندازه است: از .extraSmall (5 pt) تا .accessibilityExtraExtraExtraLarge (77 pt برای .body). ضریب مقیاسبندی برای .body از 0.85 (XS) تا 1.71 (XXXL) نسبت به مقدار پایه متغیر است. @ScaledMetric دقیقاً از این مقیاس استفاده میکند، بنابراین مقدار 12 pt برای padding ممکن است در حداکثر اندازه دسترسی به ~20 pt برسد.
| Content Size Category | ضریب (body) | مثال @ScaledMetric(12) |
|---|---|---|
| extraSmall | 0.85 | ~10 pt |
| small | 0.93 | ~11 pt |
| medium (default) | 1.00 | 12 pt |
| large | 1.07 | ~13 pt |
| extraLarge | 1.15 | ~14 pt |
| extraExtraLarge | 1.28 | ~15 pt |
| accessibilityExtraLarge | 1.47 | ~18 pt |
| accessibilityXXXL | 1.71 | ~20 pt |
انتخاب relativeTo: برای مقادیر مرتبط با متن اصلی از .body (padding, spacing در لیستها)، برای عناصر بزرگ از .title (iconSize, imageSize)، برای عناصر کوچک از .caption (اندازه badge) استفاده کنید. این تضمین میکند که عناصر با متن اطراف هماهنگ مقیاسبندی میشوند.
Dynamic Type — قابلیت iOS که به کاربر اجازه میدهد اندازه فونت سیستم را در Settings → Display & Brightness → Text Size تنظیم کند. تغییر به طور جهانی برای همه برنامهها اعمال میشود. @ScaledMetric به طور خودکار به این تغییر واکنش نشان میدهد: SwiftUI همه متغیرهای @ScaledMetric را با تغییر UIContentSizeCategory بهروز میکند.
مهم: @ScaledMetric فقط مقادیر عددی را مقیاسبندی میکند، اما فونتها را مستقیماً مدیریت نمیکند. برای فونتها از .font() با سبک متنی (.body, .title, .headline) استفاده کنید — SwiftUI به طور خودکار فونت را مقیاسبندی میکند. @ScaledMetric مقیاسبندی فونت را برای padding, spacing و اندازه عناصر تکمیل میکند.
Canvas Preview از Dynamic Type پشتیبانی میکند: در نوار ابزار Canvas یک لغزنده Text Size (A–A) برای بررسی UI در اندازههای مختلف فونت وجود دارد. از آن با @ScaledMetric استفاده کنید تا مطمئن شوید فاصلهها و اندازهها به درستی مقیاسبندی میشوند.
struct CardView: View {
@ScaledMetric private var cornerRadius: CGFloat = 16
@ScaledMetric private var spacing: CGFloat = 8
var body: some View {
VStack(spacing: spacing) {
Text("عنوان کارت")
.font(.headline)
Text("توضیح با پشتیبانی از نوع پویا")
.font(.body)
}
.padding(spacing * 2)
.background(.regularMaterial)
.cornerRadius(cornerRadius)
}
}
struct CardView_Previews: PreviewProvider {
static var previews: some View {
CardView()
.dynamicTypeSize(.large)
.previewDisplayName("Large")
CardView()
.dynamicTypeSize(.accessibility5)
.previewDisplayName("Accessibility 5")
}
}
cornerRadius از 16 pt تا ~27 pt در حداکثر اندازه دسترسی مقیاسبندی میشود. spacing — از 8 تا ~14 pt. این تضمین میکند که کارت در هر اندازه فونتی متعادل باقی میماند.
مثال: آیکون با پشتیبانی Dynamic Type. اندازه آیکونهای Image(systemName:) به طور پیشفرض تحت Dynamic Type مقیاسبندی نمیشود. @ScaledMetric این مشکل را حل میکند: با تغییر imageScale یا اندازه frame بر اساس ضریب مقیاس جاری.
struct IconLabel: View {
let title: String
let icon: String
@ScaledMetric private var iconDimension: CGFloat = 28
@ScaledMetric(relativeTo: .body) private var spacing: CGFloat = 6
var body: some View {
HStack(spacing: spacing) {
Image(systemName: icon)
.resizable()
.frame(width: iconDimension, height: iconDimension)
Text(title)
.font(.body)
}
}
}
مثال: کامپوننت badge دسترسپذیر. Badge با عدد باید به نسبت متن مقیاسبندی شود. @ScaledMetric برای حداقل اندازه badge تضمین میکند که badge دایرهای در فونت بزرگ قابل مشاهده باقی میماند.
struct BadgeView: View {
let count: Int
@ScaledMetric(relativeTo: .caption) private var badgeSize: CGFloat = 20
@ScaledMetric(relativeTo: .caption) private var fontScale: CGFloat = 1
var body: some View {
ZStack {
Circle()
.fill(.red)
.frame(width: badgeSize, height: badgeSize)
Text("\(count)")
.font(.caption)
.foregroundColor(.white)
.scaleEffect(fontScale)
}
.fixedSize()
}
}
fontScale علاوه بر این محتوای Circle را برای مطابقت با badgeSize افزایشیافته مقیاسبندی میکند. بدون fontScale متن داخل badge ممکن است در فونت بزرگ جا نشود.
@ScaledMetric و @State — هر دو property wrapperهایی هستند که تغییرات را ردیابی میکنند، اما با منابع بهروزرسانی متفاوت. @State مقدار را با تغییر برنامهای (از طریق $stateBinding) بهروز میکند. @ScaledMetric مقدار را با تغییر سیستم Dynamic Type به طور خودکار بهروز میکند، اما اجازه تغییر مستقیم مقدار از کد را نمیدهد.
تفاوت کلیدی: @ScaledMetric — read-only برای توسعهدهنده و write-only برای سیستم. شما نمیتوانید scaledValue را از طریق setter تغییر دهید — توسط SwiftUI بر اساس مقدار پایه و Dynamic Type جاری محاسبه میشود. @State برعکس، کاملاً توسط توسعهدهنده مدیریت میشود. اگر به مقداری نیاز دارید که هم تحت Dynamic Type مقیاسبندی شود و هم به صورت برنامهای تغییر کند — @ScaledMetric را با @State ترکیب کنید یا از ویژگی محاسبهشده استفاده کنید.
| ویژگی | @ScaledMetric | @State |
|---|---|---|
| منبع بهروزرسانی | Dynamic Type (سیستم) | برنامهای (توسعهدهنده) |
| نوع مقدار | CGFloat, Int, Double | هر نوع |
| تغییر از کد | غیرممکن | از طریق binding امکانپذیر |
| بازترسیم View | تغییر Dynamic Type | تغییر مقدار |
| نسخه iOS | iOS 14+ | iOS 13+ |
الگوی ترکیبی: اگر نیاز به تغییر برنامهای padding دارید (مثلاً انیمیشن کلیک) و همزمان مقیاسبندی تحت Dynamic Type، یک @ScaledMetric برای مقدار مقیاسشده پایه و یک @State برای ضریب انیمیشن ایجاد کنید. مقدار نهایی = scaledValue × animationMultiplier.
اشتباه 1: استفاده از @ScaledMetric برای فونتها. @ScaledMetric اعداد را مقیاسبندی میکند، نه فونتها. برای فونتها از .font(.body) استفاده کنید — SwiftUI به طور خودکار Dynamic Type را اعمال میکند. هرگز از @ScaledMetric با font(.system(size: scaledSize)) استفاده نکنید — این دسترسیپذیری سیستم را خراب میکند.
اشتباه 2: نداشتن relativeTo برای عناصر ناهمگن. اگر padding (مرتبط با .body) و iconSize (مرتبط با .title) دارید، برای هر کدام relativeTo صحیح را مشخص کنید. بدون relativeTo هر دو با .body مقیاسبندی میشوند که منجر به افزایش نامتناسب آیکون نسبت به بافت متنی آن میشود.
اشتباه 3: @ScaledMetric در ViewModel/@ObservableObject. @ScaledMetric یک property wrapper SwiftUI است که فقط در داخل View کار میکند. نمیتوان از آن در ViewModel یا سرویسها استفاده کرد. برای مقیاسبندی در ViewModel، مقدار مقیاسشده را از View به عنوان پارامتر ارسال کنید یا از @Environment(\.sizeCategory) در View استفاده کنید.
@Environment(\.sizeCategory) — روش جایگزین برای دریافت Dynamic Type جاری در View. زمانی استفاده کنید که به کنترل بیشتری نیاز دارید: محاسبه ضریب سفارشی، ارسال sizeCategory به ViewModel یا ترکیب با @ScaledMetric برای مقیاسبندی انعطافپذیر.
struct CustomScaledView: View {
@Environment(\.sizeCategory) private var sizeCategory
@ScaledMetric private var basePadding: CGFloat = 12
private var extraPadding: CGFloat {
if sizeCategory >= .accessibilityLarge {
return basePadding * 0.5
}
return 0
}
var body: some View {
Text("محتوای مقیاسشده سفارشی")
.font(.body)
.padding(basePadding + extraPadding)
}
}
فاصله اضافی extraPadding فقط در اندازههای دسترسی اضافه میشود و برای متن بزرگ فضای بیشتری بدون تغییر منطق پایه @ScaledMetric فراهم میکند.
سوالات متداول
@ScaledMetric — property wrapper رسمی SwiftUI برای مقیاسبندی اعداد. @ScaledFont به عنوان API استاندارد وجود ندارد — این یک wrapper سفارشی است که توسط جامعه پیادهسازی شده است. برای فونتها همیشه از .font() داخلی با سبکهای متنی (.body, .title) استفاده کنید و @ScaledMetric — برای padding, spacing و اندازهها.
@ScaledMetric در iOS 14+, watchOS 7+, tvOS 14+ و macOS 11+ در دسترس است. در watchOS Dynamic Type به محدوده کوچکتری محدود است — اندازههای .extraSmall تا .extraLarge بدون اندازههای دسترسی در دسترس هستند. در tvOS Dynamic Type وجود ندارد — @ScaledMetric همیشه مقدار پایه را برمیگرداند.
بله، برای تست @ScaledMetric یک View با @ScaledMetric ایجاد کنید و مقدار environment .sizeCategory را از طریق .environment(\.sizeCategory, .extraExtraLarge) ارسال کنید. سپس اندازه عنصر را از طریق GeometryReader یا SwiftUI Inspector دریافت کنید. alternatively, منطق مقیاسبندی را از طریق UIFontMetrics در یک ماژول جداگانه بررسی کنید.
.dynamicTypeSize — modifier View که حداکثر Dynamic Type را برای سلسلهمراتب محدود میکند (مثلاً .dynamicTypeSize(...large)). @ScaledMetric این محدودیت را در نظر میگیرد: اگر .dynamicTypeSize تنظیم شده باشد، مقدار مقیاسشده از اندازه مربوطه تجاوز نخواهد کرد. برای کنترل دقیق هر دو API را ترکیب کنید.
مطمئن شوید View از @ScaledMetric در داخل خود استفاده میکند (نه در ViewModel). بررسی کنید که View در Dynamic Type مشترک شده است: @ScaledMetric به طور خودکار بهروزرسانی body را فراخوانی میکند، اما اگر View از .equatable() یا .id() استفاده کند، مکانیزم ممکن است خراب شود. از @Environment(\.sizeCategory) به عنوان fallback استفاده کنید.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید