Sistema de color en SwiftUI: construcción de paletas de color adaptativas
Embed This Widget
Add the script tag and a data attribute to embed this widget.
Embed via iframe for maximum compatibility.
<iframe src="https://colorfyi.com/iframe/entity//" width="420" height="400" frameborder="0" style="border:0;border-radius:10px;max-width:100%" loading="lazy"></iframe>
Paste this URL in WordPress, Medium, or any oEmbed-compatible platform.
https://colorfyi.com/entity//
Add a dynamic SVG badge to your README or docs.
[](https://colorfyi.com/entity//)
Use the native HTML custom element.
El sistema de color de SwiftUI es uno de los más sofisticados de cualquier framework de UI. Va mucho más allá de almacenar un valor hex — un Color de SwiftUI puede resolverse de manera diferente según el esquema de color actual, el espacio de color activo del display, la configuración de accesibilidad del usuario e incluso el entorno en el que se renderiza. Construir una paleta de color adaptativa significa comprender todas estas dimensiones, no solo elegir un conjunto de códigos hex. Esta guía recorre el sistema completo: la diferencia entre Color y UIColor, la gestión en catálogos de assets, los colores dinámicos para modo oscuro, las extensiones de Swift para un acceso ergonómico y los patrones de color accesibles.
SwiftUI Color vs UIColor
La relación entre Color (SwiftUI) y UIColor (UIKit) es una fuente habitual de confusión. No son el mismo tipo, aunque son interconvertibles.
UIColor: la base
UIColor es una clase de UIKit que existe desde iOS 2. Representa un color que puede resolverse de forma diferente según un UITraitCollection — el objeto que describe el entorno actual (modo claro/oscuro, espacio de color del display, configuración de accesibilidad, etc.).
// UIColor con resolución dinámica
let dynamicRed = UIColor { traitCollection in
if traitCollection.userInterfaceStyle == .dark {
return UIColor(red: 1.0, green: 0.27, blue: 0.23, alpha: 1.0) // rojo más claro para fondo oscuro
} else {
return UIColor(red: 0.85, green: 0.11, blue: 0.11, alpha: 1.0) // rojo más intenso para fondo claro
}
}
Las instancias de UIColor creadas de esta forma son "dinámicas" — consultan la trait collection actual en el momento de uso, no en el momento de creación. Este es el mecanismo detrás de todos los colores del sistema como UIColor.systemBackground y UIColor.label.
SwiftUI Color: la capa moderna
SwiftUI.Color es una estructura, no una clase, y fue diseñada específicamente para UI declarativa. Envuelve una de varias representaciones subyacentes:
| Representación | Ejemplos |
|---|---|
| Color del sistema con nombre | Color.primary, Color.secondary |
| Color del catálogo de assets | Color("BrandBlue") |
| Puente UIColor | Color(uiColor: .systemRed) |
| Valor RGBA fijo | Color(red: 0.2, green: 0.5, blue: 1.0) |
| Valor OKLCH / P3 | Color(.displayP3, red: 0.2, green: 0.6, blue: 1.0) |
La distinción crítica es que un Color con un valor RGBA fijo es estático — no se adapta al modo claro/oscuro ni a la configuración de accesibilidad. Un Color respaldado por una entrada del catálogo de assets o un UIColor dinámico sí es adaptativo.
// Estático — no cambia en modo oscuro
let staticBlue = Color(red: 0.23, green: 0.51, blue: 0.96)
// Adaptativo — se resuelve correctamente en modo claro Y oscuro
let adaptiveBlue = Color("AccentBlue") // del catálogo de assets
En un sistema de color de producción, todo color que aparezca en pantalla debe ser adaptativo, no estático. Esto implica enrutar todos los colores a través del catálogo de assets o de inicializadores dinámicos de UIColor, sin codificar jamás los componentes RGB directamente en las vistas.
Conversión entre los dos tipos
En aplicaciones que mezclan SwiftUI y UIKit (que es prácticamente toda aplicación no trivial en 2026), es frecuente necesitar convertir entre los tipos:
// UIColor → Color (SwiftUI)
let swiftUIColor = Color(uiColor: UIColor.systemBlue)
// Color → UIColor (para APIs de UIKit)
let uiKitColor = UIColor(Color.accentColor)
El inicializador UIColor(Color:) se añadió en iOS 15. Para versiones anteriores, la conversión es más compleja y puede perder la resolución dinámica — otra razón más para apuntar a iOS 16+ en aplicaciones SwiftUI-first.
Gestión de colores en el catálogo de assets
El catálogo de assets de Xcode es el hogar canónico de los colores personalizados. Los colores del catálogo de assets admiten:
- Apariencias dinámicas (claro/oscuro)
- Variantes de alto contraste
- Colores de gama amplia Display P3
- Cualquier gama / gama específica
- Valores de color localizados (poco frecuente, pero soportado)
Creación de colores en el catálogo de assets
En el editor del catálogo de assets de Xcode:
- Abre
Assets.xcassets. - Haz clic en el botón
+en la parte inferior y elige Color Set. - Nombra el color — usa un nombre semántico como
BrandPrimary, no un nombre descriptivo comoDarkBlue. - En el inspector de atributos, configura Appearances como Any, Dark para habilitar variantes claro/oscuro.
- Establece Color Space como sRGB para colores estándar o Display P3 para colores de gama amplia.
- Ingresa los valores hex o RGB para las variantes de modo claro y oscuro por separado.
Color sets programáticos
Para sistemas de diseño grandes, crear docenas de conjuntos de colores manualmente en la interfaz de Xcode es tedioso y propenso a errores. Los conjuntos de colores del catálogo de assets son archivos JSON, y puedes generarlos programáticamente desde tu pipeline de design tokens:
Assets.xcassets/
└── BrandPrimary.colorset/
└── Contents.json
{
"colors": [
{
"color": {
"color-space": "srgb",
"components": {
"red": "0.114",
"green": "0.306",
"blue": "0.847",
"alpha": "1.000"
}
},
"idiom": "universal"
},
{
"appearances": [
{ "appearance": "luminosity", "value": "dark" }
],
"color": {
"color-space": "srgb",
"components": {
"red": "0.376",
"green": "0.647",
"blue": "0.980",
"alpha": "1.000"
}
},
"idiom": "universal"
}
],
"info": { "author": "xcode", "version": 1 }
}
Un formateador de Style Dictionary puede generar esta estructura JSON desde tu fuente de design tokens:
// style-dictionary-ios-colorset.js
StyleDictionary.registerFormat({
name: 'ios/colorset',
formatter: ({ token }) => {
const lightHex = token.value
const darkHex = token.darkValue ?? token.value
const toComponents = (hex) => ({
red: (parseInt(hex.slice(1, 3), 16) / 255).toFixed(3),
green: (parseInt(hex.slice(3, 5), 16) / 255).toFixed(3),
blue: (parseInt(hex.slice(5, 7), 16) / 255).toFixed(3),
alpha: '1.000',
})
return JSON.stringify({
colors: [
{
color: { 'color-space': 'srgb', components: toComponents(lightHex) },
idiom: 'universal',
},
{
appearances: [{ appearance: 'luminosity', value: 'dark' }],
color: { 'color-space': 'srgb', components: toComponents(darkHex) },
idiom: 'universal',
},
],
info: { author: 'xcode', version: 1 },
}, null, 2)
},
})
Esta automatización garantiza que cada color en el catálogo de assets permanezca sincronizado con los design tokens sin interacción manual en Xcode.
Colores dinámicos para el modo oscuro
El soporte del modo oscuro en iOS/iPadOS no es opcional para aplicaciones que apuntan a iOS 13 y versiones posteriores. Los colores del catálogo de assets con variantes Any/Dark gestionan el cambio automáticamente, pero hay matices que vale la pena entender.
Colores adaptativos del sistema
Antes de definir colores personalizados, comprueba si el sistema proporciona lo que necesitas. Los colores del sistema de Apple cubren los casos de uso más comunes y se adaptan automáticamente al modo claro, oscuro y de accesibilidad:
| Color | Modo claro | Modo oscuro | Uso |
|---|---|---|---|
.systemBackground |
#FFFFFF | #000000 | Fondo principal |
.secondarySystemBackground |
#F2F2F7 | #1C1C1E | Fondo de tabla agrupada |
.label |
#000000 | #FFFFFF | Texto principal |
.secondaryLabel |
#3C3C43 al 60% de opacidad | #EBEBF5 al 60% de opacidad | Texto secundario |
.systemBlue |
#007AFF | #0A84FF | Azul estándar |
.systemRed |
#FF3B30 | #FF453A | Error/destructivo |
.systemGreen |
#34C759 | #30D158 | Éxito |
Para la UI informativa estándar (vistas de tabla, alertas, hojas del sistema), prefiere los colores del sistema sobre los personalizados. Se adaptan automáticamente a los futuros cambios de diseño de iOS y no requieren mantenimiento.
Colores dinámicos personalizados en código
Cuando los colores del sistema son insuficientes, define colores dinámicos en código usando el inicializador basado en clausuras de UIColor:
// Colors.swift
import UIKit
import SwiftUI
enum BrandColor {
static let primary = UIColor { traits in
traits.userInterfaceStyle == .dark
? UIColor(hex: "#60A5FA") // blue-400 para fondo oscuro
: UIColor(hex: "#2563EB") // blue-600 para fondo claro
}
static let surface = UIColor { traits in
traits.userInterfaceStyle == .dark
? UIColor(hex: "#0F172A") // slate-900
: UIColor(hex: "#FFFFFF") // blanco
}
static let surfaceElevated = UIColor { traits in
traits.userInterfaceStyle == .dark
? UIColor(hex: "#1E293B") // slate-800
: UIColor(hex: "#F8FAFC") // slate-50
}
}
Para los colores SwiftUI correspondientes:
extension Color {
static let brandPrimary = Color(uiColor: BrandColor.primary)
static let brandSurface = Color(uiColor: BrandColor.surface)
static let brandSurfaceElevated = Color(uiColor: BrandColor.surfaceElevated)
}
Elección de los tonos para el modo oscuro
El principio para seleccionar tonos en modo oscuro refleja lo que aplica en diseño web: un color legible sobre un fondo claro necesita una variante más clara para mantener la legibilidad sobre un fondo oscuro. Usa el Generador de tonos para explorar el matiz de tu marca a lo largo del rango completo 50–950. Para un azul primario:
- Modo claro: rango 600–700 (azul oscuro, contrasta bien sobre blanco)
- Modo oscuro: rango 300–400 (azul más claro, contrasta bien sobre casi negro)
Ingresa el hex azul primario de tu marca en el generador de tonos. La tabla de salida permite comparar los tonos uno al lado del otro y elegir el nivel adecuado para cada contexto de superficie.
Extensiones de color personalizadas en Swift
Los inicializadores UIColor(hex:) en bruto y los literales de cadena Color("TokenName") dispersos en el código de vista son un riesgo de mantenimiento — los errores tipográficos causan fallos silenciosos en tiempo de ejecución en lugar de errores en tiempo de compilación. Una extensión de color tipada elimina esto:
Inicializador hex de UIColor
extension UIColor {
convenience init(hex: String) {
let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
var int: UInt64 = 0
Scanner(string: hex).scanHexInt64(&int)
let a, r, g, b: UInt64
switch hex.count {
case 6:
(a, r, g, b) = (255, (int >> 16) & 255, (int >> 8) & 255, int & 255)
case 8:
(a, r, g, b) = ((int >> 24) & 255, (int >> 16) & 255, (int >> 8) & 255, int & 255)
default:
(a, r, g, b) = (255, 0, 0, 0)
}
self.init(
red: CGFloat(r) / 255,
green: CGFloat(g) / 255,
blue: CGFloat(b) / 255,
alpha: CGFloat(a) / 255
)
}
}
Espacio de nombres de color tipado
En lugar de Color("BrandPrimary") en todas partes, define un espacio de nombres tipado:
// DesignSystem/Colors.swift
import SwiftUI
extension Color {
// MARK: — Marca
/// Color de acción principal. Se adapta al modo claro/oscuro.
static let brandPrimary = Color("BrandPrimary", bundle: .main)
/// Estado hover/presionado de brandPrimary. Normalmente un tono más oscuro.
static let brandPrimaryHover = Color("BrandPrimaryHover", bundle: .main)
// MARK: — Superficie
/// Fondo de la página. En la mayoría de los casos, `systemBackground`; úsalo para versiones ajustadas a la marca.
static let surface = Color("Surface", bundle: .main)
/// Superficie de tarjeta elevada. Ligeramente más clara que `surface` en modo oscuro como indicación de elevación.
static let surfaceElevated = Color("SurfaceElevated", bundle: .main)
// MARK: — Texto
/// Texto de cuerpo principal. Mayor contraste sobre la superficie actual.
static let textPrimary = Color("TextPrimary", bundle: .main)
/// Texto secundario. Metadatos, créditos, pies de foto.
static let textSecondary = Color("TextSecondary", bundle: .main)
// MARK: — Retroalimentación
/// Estado de éxito: confirmaciones, indicadores de completado.
static let feedbackSuccess = Color("FeedbackSuccess", bundle: .main)
/// Estado de advertencia: precauciones, funcionalidad degradada.
static let feedbackWarning = Color("FeedbackWarning", bundle: .main)
/// Estado de error/destructivo: fallos de validación, acciones destructivas.
static let feedbackError = Color("FeedbackError", bundle: .main)
}
El uso en las vistas se vuelve fuertemente tipado:
struct PrimaryButton: View {
let title: String
let action: () -> Void
var body: some View {
Button(action: action) {
Text(title)
.font(.headline)
.foregroundStyle(.white)
.frame(maxWidth: .infinity)
.padding(.vertical, 14)
.background(Color.brandPrimary)
.clipShape(RoundedRectangle(cornerRadius: 10))
}
}
}
Un error tipográfico en un nombre de color como Color.brandPrimry es ahora un error en tiempo de compilación en lugar de un fallo o un color de reserva invisible.
Colores de accesibilidad y modo de alto contraste
SwiftUI y UIKit admiten la configuración de accesibilidad Aumentar contraste (Ajustes → Accesibilidad → Pantalla y tamaño del texto → Aumentar contraste). Esta configuración es usada por usuarios que necesitan mayor separación visual entre elementos.
Proporcionar variantes de alto contraste
Los conjuntos de color del catálogo de assets admiten variantes de alto contraste además de claro/oscuro:
- En Xcode, selecciona tu conjunto de colores.
- En el inspector de atributos, en Appearances, habilita también High Contrast.
- Aparecen cuatro ranuras de color: Any (predeterminado), Dark, Any (alto contraste), Dark (alto contraste).
- Completa las variantes de alto contraste con proporciones de contraste más fuertes que las variantes predeterminadas.
Por ejemplo, un color gris de texto apagado (#9CA3AF) que cumple WCAG AA con 3.1:1 sobre blanco podría necesitar oscurecerse a #6B7280 (4.8:1) en modo de alto contraste.
Detección del nivel de contraste en código
Al construir colores programáticamente (no mediante catálogo de assets), comprueba la trait collection:
let adaptiveGray = UIColor { traits in
let isHighContrast = traits.accessibilityContrast == .high
let isDark = traits.userInterfaceStyle == .dark
switch (isDark, isHighContrast) {
case (false, false): return UIColor(hex: "#9CA3AF") // claro normal
case (false, true): return UIColor(hex: "#6B7280") // claro de alto contraste
case (true, false): return UIColor(hex: "#6B7280") // oscuro normal
case (true, true): return UIColor(hex: "#D1D5DB") // oscuro de alto contraste
}
}
Proporciones de contraste mínimas para iOS
Las aplicaciones iOS que apuntan a las Directrices de Interfaz Humana de Apple deben cumplir estos umbrales de contraste:
| Tipo de texto | Proporción mínima | Nivel WCAG |
|---|---|---|
| Texto normal (< 18pt) | 4.5:1 | AA |
| Texto grande (≥ 18pt o ≥ 14pt en negrita) | 3:1 | AA |
| Componentes de UI y objetos gráficos | 3:1 | AA |
| Modo de alto contraste | 7:1 recomendado | AAA |
Usa el Verificador de contraste para comprobar cualquier combinación texto/fondo antes de finalizar tu paleta. Ingresa los valores hex de primer plano y fondo en modo claro, verifica la proporción y repite para el modo oscuro. Ambos modos deben superarla de forma independiente.
Diferenciación sin depender solo del color
WCAG requiere que la información transmitida por el color también se transmita por otro medio — forma, patrón, texto o icono. Esto es crítico para usuarios daltónicos (aproximadamente el 8% de los hombres tienen alguna forma de deficiencia en la visión del color).
En la práctica para iOS:
- Errores de validación en formularios: Muestra un icono de error y texto descriptivo además de un borde rojo.
- Estados de éxito/fallo: Usa formas distintas (palomita vs. X) junto a los colores verde/rojo.
- Gráficos de datos: Usa patrones o etiquetas además de la codificación de color para las diferentes series.
- Vínculos: Asegúrate de que los vínculos estén subrayados o tengan una forma distintiva, no diferenciados solo por color.
// Correcto: estado de error comunicado por forma + color + texto
struct ValidatedTextField: View {
let text: String
let error: String?
var body: some View {
VStack(alignment: .leading, spacing: 4) {
TextField("", text: .constant(text))
.padding(10)
.background(Color.surface)
.overlay(
RoundedRectangle(cornerRadius: 8)
.strokeBorder(
error != nil ? Color.feedbackError : Color(UIColor.separator),
lineWidth: error != nil ? 2 : 1
)
)
if let error {
Label(error, systemImage: "exclamationmark.circle.fill")
.font(.caption)
.foregroundStyle(Color.feedbackError)
}
}
}
}
El error se comunica mediante tres señales: el color rojo del borde, el aumento del grosor del borde (cambio de forma) y el texto de error con un icono. Un usuario daltónico ve la forma y el texto; un usuario con visión normal ve los tres.
Conclusiones clave
- Las instancias de
Coloren SwiftUI respaldadas por entradas del catálogo de assets o clausuras dinámicas deUIColorson adaptativas — se resuelven correctamente para el modo claro/oscuro, el espacio de color del display y la configuración de accesibilidad. Las instancias deColorcon RGBA fijo son estáticas y deben evitarse en producción. - Los conjuntos de color del catálogo de assets son archivos JSON y pueden generarse programáticamente desde design tokens, manteniendo los colores iOS sincronizados con los tokens de web y otras plataformas mediante un pipeline compartido.
- Los colores dinámicos para el modo oscuro deben usar un tono más claro del mismo matiz, no un valor invertido. Usa el Generador de tonos para encontrar el tono correcto para cada contexto: rango 600–700 para modo claro, rango 300–400 para fondos en modo oscuro.
- Las extensiones de color Swift tipadas (
Color.brandPrimaryen lugar deColor("BrandPrimary")) convierten los errores tipográficos de color de fallos en tiempo de ejecución a errores en tiempo de compilación y proporcionan documentación a través del sistema de tipos de Swift. - Los catálogos de assets admiten variantes de alto contraste además de variantes claro/oscuro. Proporciónelas para cualquier color usado en texto o delimitación de componentes de UI — el Verificador de contraste comprueba que cada variante cumpla el umbral WCAG correspondiente.
- La accesibilidad requiere que la información transmitida por el color también se transmita por forma, icono o texto — esto es tanto un requisito WCAG como una necesidad práctica para el 8% de los usuarios masculinos con deficiencia en la visión del color.
Colores relacionados
Marcas relacionadas
Herramientas relacionadas
Verificador de contraste
Verifica las relaciones de contraste de color según las directrices WCAG 2.1. Prueba el cumplimiento AA y AAA para texto normal y grande.
Generador de tonos
Genera escalas de tonos estilo Tailwind CSS (50–950) a partir de cualquier color base para sistemas de diseño.