@ScaledMetric — چیست، property wrapper SwiftUI و Dynamic Type

نویسنده: IT Sectr منتشر شده: 2026-06-27 زمان مطالعه: 10 دقیقه

@ScaledMetric — property wrapper SwiftUI که به طور خودکار مقدار عددی را مطابق با تنظیمات Dynamic Type کاربر مقیاس‌بندی می‌کند. مقدار در @ScaledMetric قرار می‌گیرد و با تغییر اندازه سیستم فونت دوباره محاسبه می‌شود که دسترسی رابط را برای افراد دارای اختلالات بینایی تضمین می‌کند. طبق Apple Developer Documentation (2026)، @ScaledMetric از مقیاس UIFontMetrics برای محاسبه مقیاس نسبی بر اساس preferred content size category استفاده می‌کند. درباره accessibility بیشتر در مطلب accessibility SwiftUI بخوانید.

نکات اصلی

  • @ScaledMetric — property wrapper SwiftUI برای مقیاس‌بندی مقادیر تحت Dynamic Type.
  • Dynamic Type — تنظیم سیستم iOS که اندازه فونت را از UIFontTextStyle تغییر می‌دهد.
  • مقیاس‌بندی — @ScaledMetric مقدار پایه و ضریب relativeTo را می‌پذیرد.
  • به‌روزرسانی خودکار — با تغییر Dynamic Type، @ScaledMetric دوباره محاسبه شده و UI به‌روز می‌شود.
  • Accessibility — استفاده از @ScaledMetric دسترسی رابط را بدون کد اضافی بهبود می‌بخشد.

@ScaledMetric چیست؟

@ScaledMetric — property wrapper SwiftUI که در iOS 14 اضافه شده است و به طور خودکار مقدار عددی (CGFloat, Int, Double) را با اندازه فونت جاری Dynamic Type مقیاس‌بندی می‌کند. برخلاف .font(.body) برای فونت‌ها، @ScaledMetric هر پارامتر عددی را مقیاس‌بندی می‌کند: padding, spacing, cornerRadius, iconSize — هر چیزی که باید با متن بزرگ به نسبت افزایش یابد.

وظیفه اصلی @ScaledMetric تضمین مقیاس‌بندی دسترسی‌پذیری عناصر غیرمتنی رابط است. وقتی کاربر فونت را در تنظیمات iOS بزرگ می‌کند، دکمه‌ها، آیکون‌ها و فاصله‌ها باید به نسبت افزایش یابند تا رابط متعادل بماند. @ScaledMetric این وظیفه را به طور خودکار و بدون محاسبه دستی ضریب‌ها حل می‌کند.

سینتکس @ScaledMetric

سینتکس پایه @ScaledMetric از مقدار پیش‌فرض و پارامتر اختیاری relativeTo استفاده می‌کند. اگر relativeTo مشخص شود، مقیاس‌بندی به یک سبک متنی خاص (UIFontTextStyle) متصل می‌شود. اگر مشخص نشود — از مقیاس .body استفاده می‌شود.

swift
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 چگونه کار می‌کند

مکانیزم @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 به‌روز می‌شود.

مقیاس مقیاس‌بندی Dynamic Type

مقیاس 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)
extraSmall0.85~10 pt
small0.93~11 pt
medium (default)1.0012 pt
large1.07~13 pt
extraLarge1.15~14 pt
extraExtraLarge1.28~15 pt
accessibilityExtraLarge1.47~18 pt
accessibilityXXXL1.71~20 pt

انتخاب relativeTo: برای مقادیر مرتبط با متن اصلی از .body (padding, spacing در لیست‌ها)، برای عناصر بزرگ از .title (iconSize, imageSize)، برای عناصر کوچک از .caption (اندازه badge) استفاده کنید. این تضمین می‌کند که عناصر با متن اطراف هماهنگ مقیاس‌بندی می‌شوند.

@ScaledMetric و Dynamic Type

Dynamic Type — قابلیت iOS که به کاربر اجازه می‌دهد اندازه فونت سیستم را در Settings → Display & Brightness → Text Size تنظیم کند. تغییر به طور جهانی برای همه برنامه‌ها اعمال می‌شود. @ScaledMetric به طور خودکار به این تغییر واکنش نشان می‌دهد: SwiftUI همه متغیرهای @ScaledMetric را با تغییر UIContentSizeCategory به‌روز می‌کند.

مهم: @ScaledMetric فقط مقادیر عددی را مقیاس‌بندی می‌کند، اما فونت‌ها را مستقیماً مدیریت نمی‌کند. برای فونت‌ها از .font() با سبک متنی (.body, .title, .headline) استفاده کنید — SwiftUI به طور خودکار فونت را مقیاس‌بندی می‌کند. @ScaledMetric مقیاس‌بندی فونت را برای padding, spacing و اندازه عناصر تکمیل می‌کند.

بررسی دسترسی‌پذیری از طریق Canvas

Canvas Preview از Dynamic Type پشتیبانی می‌کند: در نوار ابزار Canvas یک لغزنده Text Size (A–A) برای بررسی UI در اندازه‌های مختلف فونت وجود دارد. از آن با @ScaledMetric استفاده کنید تا مطمئن شوید فاصله‌ها و اندازه‌ها به درستی مقیاس‌بندی می‌شوند.

swift
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. این تضمین می‌کند که کارت در هر اندازه فونتی متعادل باقی می‌ماند.

مثال‌های @ScaledMetric

مثال: آیکون با پشتیبانی Dynamic Type. اندازه آیکون‌های Image(systemName:) به طور پیش‌فرض تحت Dynamic Type مقیاس‌بندی نمی‌شود. @ScaledMetric این مشکل را حل می‌کند: با تغییر imageScale یا اندازه frame بر اساس ضریب مقیاس جاری.

swift
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 دایره‌ای در فونت بزرگ قابل مشاهده باقی می‌ماند.

swift
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 — تفاوت‌ها

@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تغییر مقدار
نسخه iOSiOS 14+iOS 13+

الگوی ترکیبی: اگر نیاز به تغییر برنامه‌ای padding دارید (مثلاً انیمیشن کلیک) و همزمان مقیاس‌بندی تحت Dynamic Type، یک @ScaledMetric برای مقدار مقیاس‌شده پایه و یک @State برای ضریب انیمیشن ایجاد کنید. مقدار نهایی = scaledValue × animationMultiplier.

اشتباهات رایج با @ScaledMetric

اشتباه 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 استفاده کنید.

دریافت sizeCategory جاری در کد

@Environment(\.sizeCategory) — روش جایگزین برای دریافت Dynamic Type جاری در View. زمانی استفاده کنید که به کنترل بیشتری نیاز دارید: محاسبه ضریب سفارشی، ارسال sizeCategory به ViewModel یا ترکیب با @ScaledMetric برای مقیاس‌بندی انعطاف‌پذیر.

swift
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 چه تفاوتی با @ScaledFont دارد؟

@ScaledMetric — property wrapper رسمی SwiftUI برای مقیاس‌بندی اعداد. @ScaledFont به عنوان API استاندارد وجود ندارد — این یک wrapper سفارشی است که توسط جامعه پیاده‌سازی شده است. برای فونت‌ها همیشه از .font() داخلی با سبک‌های متنی (.body, .title) استفاده کنید و @ScaledMetric — برای padding, spacing و اندازه‌ها.

آیا @ScaledMetric در watchOS و tvOS کار می‌کند؟

@ScaledMetric در iOS 14+, watchOS 7+, tvOS 14+ و macOS 11+ در دسترس است. در watchOS Dynamic Type به محدوده کوچکتری محدود است — اندازه‌های .extraSmall تا .extraLarge بدون اندازه‌های دسترسی در دسترس هستند. در tvOS Dynamic Type وجود ندارد — @ScaledMetric همیشه مقدار پایه را برمی‌گرداند.

آیا می‌توان @ScaledMetric را در unit-testها تست کرد؟

بله، برای تست @ScaledMetric یک View با @ScaledMetric ایجاد کنید و مقدار environment .sizeCategory را از طریق .environment(\.sizeCategory, .extraExtraLarge) ارسال کنید. سپس اندازه عنصر را از طریق GeometryReader یا SwiftUI Inspector دریافت کنید. alternatively, منطق مقیاس‌بندی را از طریق UIFontMetrics در یک ماژول جداگانه بررسی کنید.

@ScaledMetric چگونه با .dynamicTypeSize تعامل دارد؟

.dynamicTypeSize — modifier View که حداکثر Dynamic Type را برای سلسله‌مراتب محدود می‌کند (مثلاً .dynamicTypeSize(...large)). @ScaledMetric این محدودیت را در نظر می‌گیرد: اگر .dynamicTypeSize تنظیم شده باشد، مقدار مقیاس‌شده از اندازه مربوطه تجاوز نخواهد کرد. برای کنترل دقیق هر دو API را ترکیب کنید.

اگر @ScaledMetric UI را به‌روز نکرد چه باید کرد؟

مطمئن شوید View از @ScaledMetric در داخل خود استفاده می‌کند (نه در ViewModel). بررسی کنید که View در Dynamic Type مشترک شده است: @ScaledMetric به طور خودکار به‌روزرسانی body را فراخوانی می‌کند، اما اگر View از .equatable() یا .id() استفاده کند، مکانیزم ممکن است خراب شود. از @Environment(\.sizeCategory) به عنوان fallback استفاده کنید.

خلاصه

  • @ScaledMetric — property wrapper SwiftUI برای مقیاس‌بندی خودکار اعداد تحت Dynamic Type.
  • اتصال — relativeTo مقیاس را به یک سبک متنی خاص (body, title, caption) متصل می‌کند.
  • Accessibility — @ScaledMetric دسترسی رابط را بدون کدنویسی دستی بهبود می‌بخشد.
  • فقط اعداد — wrapper CGFloat, Int, Double را مقیاس‌بندی می‌کند، نه فونت‌ها.
  • محدوده — از 0.85 (XS) تا 1.71 (XXXL) نسبت به مقدار پایه.
  • Read-only — @ScaledMetric را نمی‌توان از کد تغییر داد، فقط از طریق سیستم.

ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید