@ScaledMetric — คืออะไร, property wrapper ของ SwiftUI และ Dynamic Type

ผู้แต่ง: IT Sectr เผยแพร่เมื่อ: 2026-06-27 เวลาอ่าน: 10 นาที

@ScaledMetric คือ property wrapper ของ SwiftUI ที่ปรับขนาดค่าตัวเลขโดยอัตโนมัติตามการตั้งค่า Dynamic Type ของผู้ใช้ ค่าจะถูกห่อหุ้มใน @ScaledMetric และคำนวณใหม่เมื่อขนาดฟอนต์ของระบบเปลี่ยนแปลง ซึ่งรับประกันการเข้าถึงอินเทอร์เฟซสำหรับผู้ที่มีความบกพร่องทางการมองเห็น ตาม เอกสารสำหรับนักพัฒนา Apple (2026) @ScaledMetric ใช้สเกล UIFontMetrics เพื่อคำนวณสเกลสัมพัทธ์ตาม preferred content size category เรียนรู้เพิ่มเติมเกี่ยวกับการเข้าถึงได้ใน บทความเกี่ยวกับการเข้าถึง SwiftUI

ประเด็นสำคัญ

  • @ScaledMetric — property wrapper ของ SwiftUI สำหรับปรับขนาดค่าตาม Dynamic Type
  • Dynamic Type — การตั้งค่าระบบ iOS ที่เปลี่ยนขนาดฟอนต์จาก UIFontTextStyle
  • การปรับขนาด — @ScaledMetric รับค่าฐานและตัวคูณ relativeTo
  • อัปเดตอัตโนมัติ — เมื่อ Dynamic Type เปลี่ยนแปลง @ScaledMetric จะคำนวณใหม่และ UI จะอัปเดต
  • การเข้าถึง — การใช้ @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("Submit", systemImage: "checkmark.circle.fill")
            .font(.body)
            .padding(padding)
            .imageScale(.init(rawValue: iconSize / 24) ?? .medium)
    }
}

padding จะปรับขนาดสัมพัทธ์กับ .body (ค่าเริ่มต้น) iconSize สัมพัทธ์กับ .title เมื่อข้อความใหญ่ขึ้น ระยะห่างและไอคอนจะเพิ่มขึ้นตามสัดส่วน หากไม่มี @ScaledMetric padding จะคงที่ 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 body ใหม่ @ScaledMetric คำนวณ scaledValue ใหม่ และ UI จะอัปเดตโดยอัตโนมัติผ่านกลไก PropertyWrappers ที่คล้าย @State

สเกลการปรับขนาด Dynamic Type

สเกลของ iOS มี 11 ขนาด: ตั้งแต่ .extraSmall (5 pt) ถึง .accessibilityExtraExtraExtraLarge (77 pt สำหรับ .body) ปัจจัยการปรับขนาดสำหรับ .body อยู่ระหว่าง 0.85 (XS) ถึง 1.71 (XXXL) เทียบกับค่าฐาน @ScaledMetric ใช้สเกลนี้อย่างแม่นยำ ดังนั้นค่า padding 12 pt อาจกลายเป็น ~20 pt ที่ขนาดการเข้าถึงสูงสุด

หมวดหมู่ขนาดเนื้อหาปัจจัย (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 ที่ให้ผู้ใช้ปรับขนาดฟอนต์ระบบในการตั้งค่า → จอภาพและความสว่าง → ขนาดข้อความ การเปลี่ยนแปลงมีผลทั่วโลกกับทุกแอป @ScaledMetric ตอบสนองต่อการเปลี่ยนแปลงนี้โดยอัตโนมัติ: SwiftUI อัปเดตตัวแปร @ScaledMetric ทั้งหมดเมื่อ UIContentSizeCategory เปลี่ยนแปลง

สำคัญ: @ScaledMetric ปรับขนาดเฉพาะค่าตัวเลขเท่านั้น ไม่ได้จัดการฟอนต์โดยตรง สำหรับฟอนต์ ให้ใช้ .font() กับสไตล์ข้อความ (.body, .title, .headline) — SwiftUI จะปรับขนาดฟอนต์โดยอัตโนมัติ @ScaledMetric เสริมการปรับขนาดฟอนต์สำหรับ padding, spacing และขนาดองค์ประกอบ

การทดสอบการเข้าถึงผ่าน Canvas

Canvas Preview รองรับ Dynamic Type: แถบเครื่องมือ Canvas มีแถบเลื่อนขนาดข้อความ (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("Card Title")
                .font(.headline)
            Text("Description with dynamic type support")
                .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 หรือขนาดเฟรมตามปัจจัยสเกลปัจจุบัน

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 wrappers ที่ติดตามการเปลี่ยนแปลง แต่มีแหล่งที่มาของการอัปเดตต่างกัน @State อัปเดตค่าเมื่อมีการเปลี่ยนแปลงเชิงโปรแกรม (ผ่าน $stateBinding) @ScaledMetric อัปเดตค่าโดยอัตโนมัติเมื่อ Dynamic Type ของระบบเปลี่ยนแปลง แต่ไม่อนุญาตให้เปลี่ยนค่าโดยตรงจากโค้ด

ความแตกต่างสำคัญ: @ScaledMetric เป็นแบบอ่านอย่างเดียวสำหรับนักพัฒนาและเขียนอย่างเดียวสำหรับระบบ คุณไม่สามารถเปลี่ยน scaledValue ผ่าน setter — มันถูกคำนวณโดย SwiftUI ตามค่าฐานและ Dynamic Type ปัจจุบัน ในทางกลับกัน @State ถูกควบคุมโดยนักพัฒนาอย่างสมบูรณ์ หากคุณต้องการค่าที่ทั้งปรับขนาดตาม Dynamic Type และเปลี่ยนแปลงได้เชิงโปรแกรม — ให้รวม @ScaledMetric กับ @State หรือใช้ computed property

ลักษณะ@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) ให้ระบุ relativeTe ที่ถูกต้องสำหรับแต่ละตัว หากไม่มี 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 เพื่อการปรับขนาดที่ยืดหยุ่น

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("Custom scaled content")
            .font(.body)
            .padding(basePadding + extraPadding)
    }
}

Padding เพิ่มเติม 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 tests ได้หรือไม่?

ได้ ในการทดสอบ @ScaledMetric ให้สร้าง View ที่มี @ScaledMetric และส่งค่า environment .sizeCategory ผ่าน .environment(\.sizeCategory, .extraExtraLarge) จากนั้นรับขนาดองค์ประกอบผ่าน GeometryReader หรือ SwiftUI Inspector หรือทดสอบลอจิกการปรับขนาดผ่าน UIFontMetrics ในโมดูลแยกต่างหาก

@ScaledMetric โต้ตอบกับ .dynamicTypeSize อย่างไร?

.dynamicTypeSize คือตัวปรับแต่ง View ที่จำกัด Dynamic Type สูงสุดสำหรับลำดับชั้น (เช่น .dynamicTypeSize(...large)) @ScaledMetric เคารพข้อจำกัดนี้: หากตั้งค่า .dynamicTypeSize ค่าที่ปรับขนาดแล้วจะไม่เกินขนาดที่สอดคล้องกัน รวมทั้งสอง API เพื่อการควบคุมที่แม่นยำ

จะทำอย่างไรถ้า @ScaledMetric ไม่อัปเดต UI?

ตรวจสอบว่า View ใช้ @ScaledMetric ภายใน (ไม่ใช่ใน ViewModel) ตรวจสอบว่า View สมัครสมาชิก Dynamic Type: @ScaledMetric จะ trigger การรีเฟรช body โดยอัตโนมัติ แต่ถ้า View ใช้ .equatable() หรือ .id() กลไกอาจพัง ใช้ @Environment(\.sizeCategory) เป็นตัวสำรอง

สรุป

  • @ScaledMetric — property wrapper ของ SwiftUI สำหรับปรับขนาดตัวเลขอัตโนมัติตาม Dynamic Type
  • การผูก — relativeTo ผูกสเกลกับสไตล์ข้อความที่ระบุ (body, title, caption)
  • การเข้าถึง — @ScaledMetric ปรับปรุงการเข้าถึงอินเทอร์เฟซโดยไม่ต้องใช้โค้ดด้วยตนเอง
  • เฉพาะตัวเลข — wrapper ปรับขนาด CGFloat, Int, Double แต่ไม่ใช่ฟอนต์
  • ช่วง — ตั้งแต่ 0.85 (XS) ถึง 1.71 (XXXL) เทียบกับค่าฐาน
  • อ่านอย่างเดียว — @ScaledMetric ไม่สามารถเปลี่ยนจากโค้ดได้ เปลี่ยนได้ผ่านระบบเท่านั้น

เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร

IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ

ปรึกษาโครงการ

อ่านเพิ่มเติม