@ScaledMetric — apa itu, property wrapper SwiftUI dan Dynamic Type

Penulis: IT Sectr Diterbitkan: 2026-06-27 Waktu membaca: 10 mnt

@ScaledMetric — property wrapper SwiftUI yang secara otomatis menskalakan nilai numerik sesuai dengan pengaturan Dynamic Type pengguna. Nilai dibungkus dalam @ScaledMetric dan dihitung ulang saat ukuran font sistem berubah, menjamin aksesibilitas antarmuka bagi orang dengan gangguan penglihatan. Menurut Apple Developer Documentation (2026), @ScaledMetric menggunakan skala UIFontMetrics untuk menghitung skala relatif berdasarkan preferred content size category. Baca lebih lanjut tentang aksesibilitas di materi tentang aksesibilitas SwiftUI.

Poin utama

  • @ScaledMetric — property wrapper SwiftUI untuk menskalakan nilai di bawah Dynamic Type.
  • Dynamic Type — pengaturan sistem iOS yang mengubah ukuran font dari UIFontTextStyle.
  • Penskalaan — @ScaledMetric menerima nilai dasar dan pengali relativeTo.
  • Pembaruan otomatis — saat Dynamic Type berubah, @ScaledMetric dihitung ulang dan UI diperbarui.
  • Accessibility — penggunaan @ScaledMetric meningkatkan aksesibilitas antarmuka tanpa kode tambahan.

Apa itu @ScaledMetric?

@ScaledMetric — property wrapper SwiftUI, ditambahkan di iOS 14, yang secara otomatis menskalakan nilai numerik (CGFloat, Int, Double) ke ukuran font Dynamic Type saat ini. Tidak seperti .font(.body) untuk font, @ScaledMetric menskalakan parameter numerik apa pun: padding, spacing, cornerRadius, iconSize — semua yang harus meningkat secara proporsional saat teks besar.

Tugas utama @ScaledMetric adalah menyediakan penskalaan aksesibilitas elemen antarmuka non-teks. Saat pengguna memperbesar font di pengaturan iOS, tombol, ikon, dan spasi harus meningkat secara proporsional agar antarmuka tetap seimbang. @ScaledMetric menyelesaikan tugas ini secara otomatis, tanpa perhitungan manual pengali.

Sintaks @ScaledMetric

Sintaks dasar @ScaledMetric menggunakan nilai default dan parameter opsional relativeTo. Jika relativeTo ditentukan, penskalaan terikat pada gaya teks tertentu (UIFontTextStyle). Jika tidak — skala .body digunakan.

swift
struct AccessibleButton: View {
    @ScaledMetric private var padding: CGFloat = 12
    @ScaledMetric(relativeTo: .title) private var iconSize: CGFloat = 24
    
    var body: some View {
        Label("Kirim", systemImage: "checkmark.circle.fill")
            .font(.body)
            .padding(padding)
            .imageScale(.init(rawValue: iconSize / 24) ?? .medium)
    }
}

padding akan diskalakan relatif terhadap .body (default), iconSize — relatif terhadap .title. Pada teks besar, spasi dan ikon akan meningkat secara proporsional. Tanpa @ScaledMetric, spasi akan tetap 12 pt pada ukuran font apa pun, yang menyebabkan ketidakseimbangan visual.

Bagaimana @ScaledMetric bekerja

Mekanisme @ScaledMetric didasarkan pada UIFontMetrics dari UIKit. Saat SwiftUI membuat instance @ScaledMetric, ia menghitung pengali berdasarkan preferred content size category saat ini (UIContentSizeCategory). Nilai dasar dikalikan dengan scaledValue dari UIFontMetrics untuk gaya teks yang ditentukan.

Secara matematis: ScaledMetricValue = baseValue × UIFontMetrics.scaledValue(for: relativeTo). Jika relativeTo tidak ditentukan, UIFontMetrics.default yang terikat pada .body digunakan. Saat Dynamic Type berubah, SwiftUI membuat ulang body View, @ScaledMetric menghitung scaledValue baru dan UI diperbarui secara otomatis melalui mekanisme PropertyWrappers mirip @State.

Skala penskalaan Dynamic Type

Skala iOS mencakup 11 ukuran: dari .extraSmall (5 pt) hingga .accessibilityExtraExtraExtraLarge (77 pt untuk .body). Koefisien penskalaan untuk .body bervariasi dari 0.85 (XS) hingga 1.71 (XXXL) relatif terhadap nilai dasar. @ScaledMetric menggunakan skala ini, jadi nilai 12 pt untuk padding dapat menjadi ~20 pt pada ukuran aksesibilitas maksimum.

Content Size CategoryKoefisien (body)Contoh @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

Pemilihan relativeTo: gunakan .body untuk nilai yang terkait dengan teks utama (padding, spacing dalam daftar), .title untuk elemen besar (iconSize, imageSize), .caption untuk elemen kecil (ukuran badge). Ini memastikan elemen menskala seiring dengan teks di sekitarnya.

@ScaledMetric dan Dynamic Type

Dynamic Type — fitur iOS yang memungkinkan pengguna menyesuaikan ukuran font sistem di Settings → Display & Brightness → Text Size. Perubahan diterapkan secara global ke semua aplikasi. @ScaledMetric bereaksi terhadap perubahan ini secara otomatis: SwiftUI memperbarui semua variabel @ScaledMetric saat UIContentSizeCategory berubah.

Penting: @ScaledMetric hanya menskalakan nilai numerik, tetapi tidak mengelola font secara langsung. Untuk font gunakan .font() dengan gaya teks (.body, .title, .headline) — SwiftUI secara otomatis menskalakan font. @ScaledMetric melengkapi penskalaan font untuk padding, spacing, dan ukuran elemen.

Memeriksa aksesibilitas melalui Canvas

Canvas Preview mendukung Dynamic Type: di bilah alat Canvas ada penggeser Text Size (A–A) untuk memeriksa UI pada berbagai ukuran font. Gunakan dengan @ScaledMetric untuk memastikan spasi dan ukuran menskala dengan benar.

swift
struct CardView: View {
    @ScaledMetric private var cornerRadius: CGFloat = 16
    @ScaledMetric private var spacing: CGFloat = 8
    
    var body: some View {
        VStack(spacing: spacing) {
            Text("Judul kartu")
                .font(.headline)
            Text("Deskripsi dengan dukungan dynamic type")
                .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 menskala dari 16 pt hingga ~27 pt pada ukuran aksesibilitas maksimum. spacing — dari 8 hingga ~14 pt. Ini memastikan kartu tetap seimbang secara visual pada ukuran font apa pun.

Contoh @ScaledMetric

Contoh: ikon dengan dukungan Dynamic Type. Ukuran ikon Image(systemName:) secara default tidak diskalakan di bawah Dynamic Type. @ScaledMetric memecahkan masalah ini: dengan mengubah imageScale atau ukuran frame berdasarkan faktor skala saat ini.

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)
        }
    }
}

Contoh: komponen badge yang dapat diakses. Badge dengan angka harus menskala secara proporsional dengan teks. @ScaledMetric untuk ukuran minimum badge memastikan badge bundar tetap terlihat pada font besar.

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 juga menskalakan konten Circle agar sesuai dengan badgeSize yang diperbesar. Tanpa fontScale, teks di dalam badge mungkin tidak muat pada font besar.

@ScaledMetric vs @State — perbedaan

@ScaledMetric dan @State — keduanya property wrapper yang melacak perubahan, tetapi dengan sumber pembaruan berbeda. @State memperbarui nilai saat perubahan pemrograman (melalui $stateBinding). @ScaledMetric memperbarui nilai secara otomatis saat Dynamic Type sistem berubah, tetapi tidak mengizinkan perubahan nilai langsung dari kode.

Perbedaan utama: @ScaledMetric — read-only untuk pengembang dan write-only untuk sistem. Anda tidak dapat mengubah scaledValue melalui setter — dihitung oleh SwiftUI berdasarkan nilai dasar dan Dynamic Type saat ini. @State, sebaliknya, sepenuhnya dikelola oleh pengembang. Jika Anda memerlukan nilai yang baik menskala di bawah Dynamic Type maupun berubah secara terprogram — gabungkan @ScaledMetric dengan @State atau gunakan properti terhitung.

Karakteristik@ScaledMetric@State
Sumber pembaruanDynamic Type (sistem)Terprogram (pengembang)
Tipe nilaiCGFloat, Int, DoubleSemua tipe
Perubahan dari kodeTidak bisaBisa melalui binding
Gambar ulang ViewSaat Dynamic Type berubahSaat nilai berubah
Versi iOSiOS 14+iOS 13+

Pola gabungan: jika Anda perlu mengubah padding secara terprogram (misalnya, animasi tekan) dan sekaligus menskalakan di bawah Dynamic Type, buat @ScaledMetric untuk nilai skala dasar dan @State untuk pengali animasi. Nilai akhir = scaledValue × animationMultiplier.

Kesalahan umum dengan @ScaledMetric

Kesalahan 1: menggunakan @ScaledMetric untuk font. @ScaledMetric menskalakan angka, bukan font. Untuk font gunakan .font(.body) — SwiftUI secara otomatis menerapkan Dynamic Type. Jangan pernah menggunakan @ScaledMetric dengan font(.system(size: scaledSize)) — ini merusak aksesibilitas sistem.

Kesalahan 2: tidak ada relativeTo untuk elemen heterogen. Jika Anda memiliki padding (terkait dengan .body) dan iconSize (terkait dengan .title), tentukan relativeTo yang benar untuk masing-masing. Tanpa relativeTo, keduanya akan diskalakan menurut .body, yang menyebabkan peningkatan ikon yang tidak proporsional relatif terhadap konteks teksnya.

Kesalahan 3: @ScaledMetric di ViewModel/@ObservableObject. @ScaledMetric adalah property wrapper SwiftUI yang hanya berfungsi di dalam View. Tidak dapat digunakan di ViewModel atau layanan. Untuk penskalaan di ViewModel, kirim nilai skala dari View sebagai parameter atau gunakan @Environment(\.sizeCategory) di View.

Mendapatkan sizeCategory saat ini dalam kode

@Environment(\.sizeCategory) — cara alternatif untuk mendapatkan Dynamic Type saat ini di View. Gunakan saat Anda membutuhkan lebih banyak kontrol: menghitung pengali kustom, mengirimkan sizeCategory ke ViewModel, atau menggabungkan dengan @ScaledMetric untuk penskalaan fleksibel.

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("Konten skala kustom")
            .font(.body)
            .padding(basePadding + extraPadding)
    }
}

Spasi tambahan extraPadding ditambahkan hanya pada ukuran aksesibilitas, memberikan lebih banyak ruang untuk teks besar tanpa mengubah logika dasar @ScaledMetric.

Pertanyaan yang sering diajukan

Apa perbedaan @ScaledMetric dengan @ScaledFont?

@ScaledMetric — property wrapper resmi SwiftUI untuk menskalakan angka. @ScaledFont tidak ada sebagai API standar — ini adalah wrapper kustom yang diimplementasikan oleh komunitas. Untuk font selalu gunakan .font() bawaan dengan gaya teks (.body, .title), dan @ScaledMetric — untuk padding, spacing, dan ukuran.

Apakah @ScaledMetric berfungsi di watchOS dan tvOS?

@ScaledMetric tersedia di iOS 14+, watchOS 7+, tvOS 14+ dan macOS 11+. Di watchOS, Dynamic Type terbatas pada rentang yang lebih kecil — ukuran dari .extraSmall hingga .extraLarge tanpa ukuran aksesibilitas. Di tvOS, Dynamic Type tidak ada — @ScaledMetric selalu mengembalikan nilai dasar.

Bisakah @ScaledMetric diuji dalam unit-test?

Ya, untuk menguji @ScaledMetric buat View dengan @ScaledMetric dan kirim nilai environment .sizeCategory melalui .environment(\.sizeCategory, .extraExtraLarge). Kemudian dapatkan ukuran elemen melalui GeometryReader atau SwiftUI Inspector. Alternatifnya, periksa logika penskalaan melalui UIFontMetrics di modul terpisah.

Bagaimana @ScaledMetric berinteraksi dengan .dynamicTypeSize?

.dynamicTypeSize — modifier View yang membatasi Dynamic Type maksimum untuk hierarki (misalnya, .dynamicTypeSize(...large)). @ScaledMetric mempertimbangkan batasan ini: jika .dynamicTypeSize diatur, nilai skala tidak akan melebihi ukuran yang sesuai. Gabungkan kedua API untuk kontrol yang presisi.

Apa yang harus dilakukan jika @ScaledMetric tidak memperbarui UI?

Pastikan View menggunakan @ScaledMetric di dalamnya (bukan di ViewModel). Periksa bahwa View berlangganan Dynamic Type: @ScaledMetric secara otomatis memicu penyegaran body, tetapi jika View menggunakan .equatable() atau .id(), mekanisme bisa rusak. Gunakan @Environment(\.sizeCategory) sebagai fallback.

Ringkasan

  • @ScaledMetric — property wrapper SwiftUI untuk penskalaan otomatis angka di bawah Dynamic Type.
  • Ikatan — relativeTo mengikat skala ke gaya teks tertentu (body, title, caption).
  • Accessibility — @ScaledMetric meningkatkan aksesibilitas antarmuka tanpa kode manual.
  • Hanya angka — wrapper menskalakan CGFloat, Int, Double, tetapi bukan font.
  • Rentang — dari 0.85 (XS) hingga 1.71 (XXXL) relatif terhadap nilai dasar.
  • Read-only — @ScaledMetric tidak dapat diubah dari kode, hanya melalui sistem.

Kami akan mengembangkan aplikasi seluler turnkey

IT Sectr membuat aplikasi iOS dan Android untuk startup dan bisnis sejak 2017. Kami akan memberi saran dan mengusulkan solusi terbaik.

Diskusikan proyek

Baca juga