@ScaledMetric — o que é, property wrapper do SwiftUI e Dynamic Type

Autor: IT Sectr Publicado: 2026-06-27 Tempo de leitura: 10 min

@ScaledMetric é um property wrapper do SwiftUI que dimensiona automaticamente um valor numérico de acordo com as configurações de Dynamic Type do usuário. O valor é envolvido em @ScaledMetric e recalculado quando o tamanho da fonte do sistema muda, garantindo acessibilidade da interface para pessoas com deficiência visual. De acordo com a Apple Developer Documentation (2026), o @ScaledMetric usa a escala UIFontMetrics para calcular a escala relativa com base na preferred content size category. Saiba mais sobre acessibilidade no artigo de acessibilidade SwiftUI.

Principais pontos

  • @ScaledMetric — um property wrapper SwiftUI para dimensionar valores para Dynamic Type.
  • Dynamic Type — uma configuração do sistema iOS que altera o tamanho da fonte de UIFontTextStyle.
  • Dimensionamento — @ScaledMetric recebe um valor base e um multiplicador relativeTo.
  • Atualização automática — quando Dynamic Type muda, @ScaledMetric recalcula e a UI é atualizada.
  • Acessibilidade — usar @ScaledMetric melhora a acessibilidade da interface sem código adicional.

O que é @ScaledMetric?

@ScaledMetric é um property wrapper do SwiftUI, adicionado no iOS 14, que dimensiona automaticamente um valor numérico (CGFloat, Int, Double) para o tamanho de fonte Dynamic Type atual. Ao contrário de .font(.body) para fontes, @ScaledMetric dimensiona qualquer parâmetro numérico: padding, spacing, cornerRadius, iconSize — tudo que deve aumentar proporcionalmente com texto maior.

O principal propósito de @ScaledMetric é fornecer dimensionamento de acessibilidade para elementos de interface não textuais. Quando o usuário aumenta o tamanho da fonte nas configurações do iOS, botões, ícones e espaçamentos devem dimensionar proporcionalmente para manter a interface equilibrada. O @ScaledMetric lida com isso automaticamente, sem cálculo manual de multiplicadores.

Sintaxe do @ScaledMetric

Sintaxe básica do @ScaledMetric usa um valor padrão e um parâmetro opcional relativeTo. Se relativeTo for especificado, o dimensionamento é vinculado a um estilo de texto específico (UIFontTextStyle). Se não for, a escala .body é usada.

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 será dimensionado em relação a .body (padrão), iconSize em relação a .title. Com texto maior, os espaçamentos e ícones aumentarão proporcionalmente. Sem @ScaledMetric, o padding permaneceria em 12 pt independentemente do tamanho da fonte, causando desequilíbrio visual.

Como @ScaledMetric funciona

O mecanismo @ScaledMetric é baseado em UIFontMetrics do UIKit. Quando o SwiftUI cria uma instância de @ScaledMetric, ele calcula um multiplicador com base na preferred content size category atual (UIContentSizeCategory). O valor base é multiplicado pelo scaledValue do UIFontMetrics para o estilo de texto especificado.

Matematicamente: ScaledMetricValue = baseValue × UIFontMetrics.scaledValue(for: relativeTo). Se relativeTo não for especificado, UIFontMetrics.default é usado, vinculado a .body. Quando Dynamic Type muda, o SwiftUI recria o corpo da View, @ScaledMetric calcula um novo scaledValue e a UI é atualizada automaticamente através do mecanismo de PropertyWrappers semelhante a @State.

Escala de dimensionamento Dynamic Type

A escala do iOS inclui 11 tamanhos: de .extraSmall (5 pt) a .accessibilityExtraExtraExtraLarge (77 pt para .body). O fator de dimensionamento para .body varia de 0.85 (XS) a 1.71 (XXXL) em relação ao valor base. O @ScaledMetric usa exatamente esta escala, então um valor de padding de 12 pt pode se tornar ~20 pt no tamanho máximo de acessibilidade.

Categoria de tamanho de conteúdoFator (body)Exemplo @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

Escolhendo relativeTo: use .body para valores relacionados ao texto do corpo (padding, spacing em listas), .title para elementos grandes (iconSize, imageSize), .caption para elementos pequenos (tamanho do badge). Isso garante que os elementos dimensionem em sincronia com o texto ao redor.

@ScaledMetric e Dynamic Type

Dynamic Type é um recurso do iOS que permite aos usuários ajustar o tamanho da fonte do sistema em Ajustes → Tela e Brilho → Tamanho do Texto. A mudança se aplica globalmente a todos os aplicativos. O @ScaledMetric reage a essa mudança automaticamente: o SwiftUI atualiza todas as variáveis @ScaledMetric quando UIContentSizeCategory muda.

Importante: @ScaledMetric dimensiona apenas valores numéricos, não gerencia fontes diretamente. Para fontes, use .font() com um estilo de texto (.body, .title, .headline) — o SwiftUI dimensiona a fonte automaticamente. O @ScaledMetric complementa o dimensionamento de fontes para padding, spacing e tamanhos de elementos.

Teste de acessibilidade via Canvas

Canvas Preview suporta Dynamic Type: a barra de ferramentas do Canvas tem um controle deslizante de Tamanho do Texto (A–A) para testar a UI em diferentes tamanhos de fonte. Use-o com @ScaledMetric para verificar se os espaçamentos e tamanhos dimensionam corretamente.

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 dimensiona de 16 pt para ~27 pt no tamanho máximo de acessibilidade. spacing — de 8 para ~14 pt. Isso garante que o cartão permaneça visualmente equilibrado em qualquer tamanho de fonte.

Exemplos de @ScaledMetric

Exemplo: ícone com suporte a Dynamic Type. Os tamanhos de ícone Image(systemName:) não dimensionam para Dynamic Type por padrão. O @ScaledMetric resolve este problema alterando o imageScale ou o tamanho do frame com base no fator de escala atual.

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

Exemplo: componente badge acessível. Um badge numerado deve dimensionar proporcionalmente ao texto. O @ScaledMetric para o tamanho mínimo do badge garante que o badge circular permaneça visível com texto grande.

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 dimensiona adicionalmente o conteúdo do Circle para corresponder ao badgeSize ampliado. Sem fontScale, o texto dentro do badge pode não caber com texto grande.

@ScaledMetric vs @State — diferenças

@ScaledMetric e @State são ambos property wrappers que rastreiam mudanças, mas com fontes de atualização diferentes. @State atualiza o valor em mudança programática (através de $stateBinding). @ScaledMetric atualiza o valor automaticamente quando o Dynamic Type do sistema muda, mas não permite alterar o valor diretamente do código.

Diferença chave: @ScaledMetric é somente leitura para o desenvolvedor e somente escrita para o sistema. Você não pode alterar o scaledValue via um setter — ele é calculado pelo SwiftUI com base no valor base e no Dynamic Type atual. @State, por outro lado, é totalmente controlado pelo desenvolvedor. Se você precisar de um valor que tanto dimensione para Dynamic Type quanto mude programaticamente — combine @ScaledMetric com @State ou use uma propriedade computada.

Característica@ScaledMetric@State
Fonte de atualizaçãoDynamic Type (sistema)Programática (desenvolvedor)
Tipo de valorCGFloat, Int, DoubleQualquer
Alteração do códigoNão permitidaPermitida via binding
Redesenho da ViewNa mudança de Dynamic TypeNa mudança de valor
Versão iOSiOS 14+iOS 13+

Padrão combinado: se você precisar alterar o padding programaticamente (por exemplo, animação de toque) enquanto dimensiona para Dynamic Type, crie um @ScaledMetric para o valor dimensionado base e um @State para o multiplicador de animação. Valor final = scaledValue × animationMultiplier.

Erros comuns com @ScaledMetric

Erro 1: Usar @ScaledMetric para fontes. @ScaledMetric dimensiona números, não fontes. Para fontes, use .font(.body) — SwiftUI aplica Dynamic Type automaticamente. Nunca use @ScaledMetric com font(.system(size: scaledSize)) — isso quebra a acessibilidade do sistema.

Erro 2: Falta de relativeTo para elementos heterogêneos. Se você tem padding (vinculado a .body) e iconSize (vinculado a .title), especifique o relativeTo correto para cada um. Sem relativeTo, ambos dimensionarão por .body, resultando em dimensionamento desproporcional do ícone em relação ao seu contexto de texto.

Erro 3: @ScaledMetric em ViewModel/@ObservableObject. @ScaledMetric é um property wrapper do SwiftUI que funciona apenas dentro de uma View. Não pode ser usado em ViewModels ou serviços. Para dimensionamento em ViewModel, passe o valor dimensionado da View como parâmetro ou use @Environment(\.sizeCategory) na View.

Obtendo a categoria de tamanho atual no código

@Environment(\.sizeCategory) é uma forma alternativa de obter o Dynamic Type atual em uma View. Use-o quando precisar de mais controle: calcular um multiplicador personalizado, passar sizeCategory para um ViewModel ou combiná-lo com @ScaledMetric para dimensionamento flexível.

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 extra extraPadding é adicionado apenas em tamanhos de acessibilidade, dando mais espaço para texto grande sem alterar a lógica base do @ScaledMetric.

Perguntas frequentes

Como @ScaledMetric é diferente de @ScaledFont?

@ScaledMetric é um property wrapper oficial do SwiftUI para dimensionar números. @ScaledFont não existe como uma API padrão — é um wrapper personalizado implementado pela comunidade. Para fontes, sempre use o .font() integrado com estilos de texto (.body, .title), e @ScaledMetric para padding, spacing e tamanhos.

@ScaledMetric funciona no watchOS e tvOS?

@ScaledMetric está disponível no iOS 14+, watchOS 7+, tvOS 14+ e macOS 11+. No watchOS, Dynamic Type é limitado a uma faixa menor — tamanhos de .extraSmall a .extraLarge estão disponíveis sem tamanhos de acessibilidade. No tvOS, Dynamic Type não está disponível — @ScaledMetric sempre retorna o valor base.

Pode-se testar @ScaledMetric em testes unitários?

Sim, para testar @ScaledMetric crie uma View com @ScaledMetric e passe o valor de ambiente .sizeCategory via .environment(\.sizeCategory, .extraExtraLarge). Em seguida, obtenha o tamanho do elemento através de GeometryReader ou SwiftUI Inspector. Alternativamente, teste a lógica de dimensionamento através de UIFontMetrics em um módulo separado.

Como @ScaledMetric interage com .dynamicTypeSize?

.dynamicTypeSize é um modificador de View que limita o Dynamic Type máximo para uma hierarquia (por exemplo, .dynamicTypeSize(...large)). @ScaledMetric respeita esse limite: se .dynamicTypeSize estiver definido, o valor dimensionado não excederá o tamanho correspondente. Combine ambas as APIs para controle preciso.

O que fazer se @ScaledMetric não atualizar a UI?

Certifique-se de que a View use @ScaledMetric internamente (não em um ViewModel). Verifique se a View assina Dynamic Type: @ScaledMetric aciona automaticamente a atualização do body, mas se a View usar .equatable() ou .id(), o mecanismo pode quebrar. Use @Environment(\.sizeCategory) como fallback.

Resumo

  • @ScaledMetric — um property wrapper SwiftUI para dimensionamento automático de números para Dynamic Type.
  • Vinculação — relativeTo vincula a escala a um estilo de texto específico (body, title, caption).
  • Acessibilidade — @ScaledMetric melhora a acessibilidade da interface sem código manual.
  • Apenas números — o wrapper dimensiona CGFloat, Int, Double, mas não fontes.
  • Faixa — de 0.85 (XS) a 1.71 (XXXL) em relação ao valor base.
  • Somente leitura — @ScaledMetric não pode ser alterado do código, apenas pelo sistema.

Vamos desenvolver um aplicativo móvel chave na mão

A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.

Discutir o projeto

Leia também