@ScaledMetric — 정의, SwiftUI property wrapper 및 Dynamic Type

저자: IT Sectr 게시일: 2026-06-27 읽는 시간: 10 분

@ScaledMetric은 사용자의 Dynamic Type 설정에 따라 숫자 값을 자동으로 조정하는 SwiftUI property wrapper입니다. 값은 @ScaledMetric에 래핑되며 시스템 폰트 크기가 변경될 때 다시 계산되어 시각 장애가 있는 사람들을 위한 인터페이스 접근성을 보장합니다. Apple Developer Documentation(2026)에 따르면 @ScaledMetric은 UIFontMetrics 척도를 사용하여 preferred content size category를 기반으로 상대적 척도를 계산합니다. 접근성에 대한 자세한 내용은 SwiftUI 접근성 문서를 참조하세요.

핵심 요점

  • @ScaledMetric — Dynamic Type에 맞게 값을 스케일링하는 SwiftUI property wrapper.
  • Dynamic Type — UIFontTextStyle에서 폰트 크기를 변경하는 iOS 시스템 설정.
  • 스케일링 — @ScaledMetric은 기본값과 relativeTo 승수를 사용합니다.
  • 자동 업데이트 — Dynamic Type이 변경되면 @ScaledMetric이 다시 계산되고 UI가 업데이트됩니다.
  • 접근성 — @ScaledMetric을 사용하면 추가 코드 없이 인터페이스 접근성이 향상됩니다.

@ScaledMetric이란?

@ScaledMetric은 iOS 14에 추가된 SwiftUI property wrapper로, 숫자 값(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은 폰트 크기와 관계없이 12pt로 유지되어 시각적 불균형이 발생합니다.

@ScaledMetric 작동 방식

@ScaledMetric 메커니즘은 UIKit의 UIFontMetrics를 기반으로 합니다. SwiftUI가 @ScaledMetric 인스턴스를 생성하면 현재 preferred content size category(UIContentSizeCategory)를 기반으로 승수를 계산합니다. 기본값은 지정된 텍스트 스타일에 대한 UIFontMetrics의 scaledValue로 곱해집니다.

수학적으로: ScaledMetricValue = baseValue × UIFontMetrics.scaledValue(for: relativeTo). relativeTo가 지정되지 않으면 .body에 바인딩된 UIFontMetrics.default가 사용됩니다. Dynamic Type이 변경되면 SwiftUI가 View 본문을 다시 만들고, @ScaledMetric이 새 scaledValue를 계산하며, UI는 @State와 유사한 PropertyWrappers 메커니즘을 통해 자동으로 업데이트됩니다.

Dynamic Type 스케일링 척도

iOS 척도는 11가지 크기를 포함합니다: .extraSmall(5pt)부터 .accessibilityExtraExtraExtraLarge(.body의 경우 77pt)까지입니다. .body의 스케일링 계수는 기본값을 기준으로 0.85(XS)에서 1.71(XXXL)까지입니다. @ScaledMetric은 정확히 이 척도를 사용하므로 12pt padding 값은 최대 접근성 크기에서 약 20pt가 될 수 있습니다.

콘텐츠 크기 카테고리계수 (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 선택: 본문 텍스트와 관련된 값(목록의 padding, spacing)에는 .body를, 큰 요소(iconSize, imageSize)에는 .title을, 작은 요소(배지 크기)에는 .caption을 사용하세요. 이렇게 하면 요소가 주변 텍스트와 동기화되어 스케일링됩니다.

@ScaledMetric과 Dynamic Type

Dynamic Type은 사용자가 설정 → 디스플레이 및 밝기 → 텍스트 크기에서 시스템 폰트 크기를 조정할 수 있는 iOS 기능입니다. 변경 사항은 모든 앱에 전역적으로 적용됩니다. @ScaledMetric은 이 변경에 자동으로 반응합니다: UIContentSizeCategory가 변경되면 SwiftUI가 모든 @ScaledMetric 변수를 업데이트합니다.

중요: @ScaledMetric은 숫자 값만 스케일링하며 폰트를 직접 관리하지 않습니다. 폰트의 경우 텍스트 스타일(.body, .title, .headline)과 함께 .font()를 사용하세요 — SwiftUI가 자동으로 폰트를 스케일링합니다. @ScaledMetric은 padding, spacing 및 요소 크기에 대한 폰트 스케일링을 보완합니다.

Canvas를 통한 접근성 테스트

Canvas Preview는 Dynamic Type을 지원합니다: Canvas 도구 모음에는 다양한 폰트 크기에서 UI를 테스트할 수 있는 텍스트 크기 슬라이더(A–A)가 있습니다. @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는 최대 접근성 크기에서 16pt에서 약 27pt로 스케일링됩니다. spacing은 8에서 약 14pt로 스케일링됩니다. 이렇게 하면 카드가 어떤 폰트 크기에서도 시각적으로 균형을 유지합니다.

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

예제: 접근 가능한 배지 컴포넌트. 번호가 매겨진 배지는 텍스트에 비례하여 스케일링되어야 합니다. 최소 배지 크기에 @ScaledMetric을 사용하면 큰 텍스트에서도 원형 배지가 계속 표시됩니다.

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은 확대된 badgeSize에 맞게 Circle 콘텐츠를 추가로 스케일링합니다. fontScale이 없으면 큰 텍스트에서 배지 내부의 텍스트가 맞지 않을 수 있습니다.

@ScaledMetric vs @State — 차이점

@ScaledMetric@State는 모두 변경 사항을 추적하는 property wrapper이지만 업데이트 소스가 다릅니다. @State는 프로그래매틱 변경($stateBinding을 통해) 시 값을 업데이트합니다. @ScaledMetric은 시스템 Dynamic Type이 변경될 때 자동으로 값을 업데이트하지만 코드에서 직접 값을 변경할 수는 없습니다.

핵심 차이점: @ScaledMetric은 개발자에게 읽기 전용이고 시스템에게 쓰기 전용입니다. setter를 통해 scaledValue를 변경할 수 없습니다 — SwiftUI가 기본값과 현재 Dynamic Type을 기반으로 계산합니다. 반면 @State는 개발자가 완전히 제어합니다. Dynamic Type에 따라 스케일링되면서 프로그래매틱하게도 변경되는 값이 필요하다면 @ScaledMetric을 @State와 결합하거나 계산된 속성을 사용하세요.

특성@ScaledMetric@State
업데이트 소스Dynamic Type (시스템)프로그래매틱 (개발자)
값 유형CGFloat, Int, Double모든 유형
코드 변경불가능binding을 통해 가능
View 다시 그리기Dynamic Type 변경 시값 변경 시
iOS 버전iOS 14+iOS 13+

결합 패턴: Dynamic Type에 맞게 스케일링하면서 프로그래매틱하게 padding을 변경해야 하는 경우(예: 탭 애니메이션), 기본 스케일 값에 @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: ViewModel/@ObservableObject에서 @ScaledMetric 사용. @ScaledMetric은 View 내에서만 작동하는 SwiftUI property wrapper입니다. ViewModel이나 서비스에서는 사용할 수 없습니다. ViewModel에서 스케일링하려면 View에서 스케일된 값을 매개변수로 전달하거나 View에서 @Environment(\.sizeCategory)를 사용하세요.

코드에서 현재 크기 카테고리 가져오기

@Environment(\.sizeCategory)는 View에서 현재 Dynamic Type을 가져오는 대체 방법입니다. 사용자 정의 승수 계산, ViewModel에 sizeCategory 전달, @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은 숫자 스케일링을 위한 공식 SwiftUI property wrapper입니다. @ScaledFont는 표준 API로 존재하지 않습니다 — 커뮤니티에서 구현한 사용자 정의 wrapper입니다. 폰트의 경우 항상 텍스트 스타일(.body, .title)과 함께 내장 .font()를 사용하고 padding, spacing 및 크기에는 @ScaledMetric을 사용하세요.

@ScaledMetric이 watchOS와 tvOS에서 작동하나요?

@ScaledMetric은 iOS 14+, watchOS 7+, tvOS 14+ 및 macOS 11+에서 사용할 수 있습니다. watchOS에서 Dynamic Type은 더 작은 범위로 제한됩니다 — 접근성 크기 없이 .extraSmall에서 .extraLarge까지의 크기를 사용할 수 있습니다. tvOS에서는 Dynamic Type을 사용할 수 없습니다 — @ScaledMetric은 항상 기본값을 반환합니다.

@ScaledMetric을 단위 테스트에서 테스트할 수 있나요?

네, @ScaledMetric을 테스트하려면 @ScaledMetric이 있는 View를 만들고 .environment(\.sizeCategory, .extraExtraLarge)를 통해 환경 .sizeCategory 값을 전달하세요. 그런 다음 GeometryReader 또는 SwiftUI Inspector를 통해 요소 크기를 가져옵니다. 또는 별도 모듈에서 UIFontMetrics를 통해 스케일링 로직을 테스트하세요.

@ScaledMetric은 .dynamicTypeSize와 어떻게 상호작용하나요?

.dynamicTypeSize는 계층 구조의 최대 Dynamic Type을 제한하는 View 수정자입니다(예: .dynamicTypeSize(...large)). @ScaledMetric은 이 제한을 따릅니다: .dynamicTypeSize가 설정되면 스케일된 값이 해당 크기를 초과하지 않습니다. 정밀한 제어를 위해 두 API를 결합하세요.

@ScaledMetric이 UI를 업데이트하지 않으면 어떻게 하나요?

View가 내부적으로 @ScaledMetric을 사용하는지 확인하세요(ViewModel이 아님). View가 Dynamic Type을 구독하는지 확인하세요: @ScaledMetric은 자동으로 body 새로고침을 트리거하지만 View가 .equatable() 또는 .id()를 사용하는 경우 메커니즘이 손상될 수 있습니다. 폴백으로 @Environment(\.sizeCategory)를 사용하세요.

요약

  • @ScaledMetric — Dynamic Type에 자동으로 숫자를 스케일링하는 SwiftUI property wrapper.
  • 바인딩 — relativeTo가 특정 텍스트 스타일(body, title, caption)에 척도를 바인딩합니다.
  • 접근성 — @ScaledMetric이 수동 코드 없이 인터페이스 접근성을 향상시킵니다.
  • 숫자만 — wrapper는 CGFloat, Int, Double을 스케일링하지만 폰트는 스케일링하지 않습니다.
  • 범위 — 기본값 기준 0.85(XS)에서 1.71(XXXL)까지.
  • 읽기 전용 — @ScaledMetric은 코드에서 변경할 수 없으며 시스템을 통해서만 변경 가능합니다.

턴키 방식의 모바일 애플리케이션을 개발해 드립니다

IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.

프로젝트 논의

더 읽어보기