Si estás inmerso en la programación Swift y estás construyendo interfaces modernas con SwiftUI, es muy probable que te hayas topado con un muro que todos hemos enfrentado: intentar personalizar un elemento nativo y darte cuenta de que el framework parece resistirse. Uno de los casos más notorios y frustrantes para cualquier iOS Developer es intentar cambiar la fuente del Picker en SwiftUI.
Aunque SwiftUI es una herramienta maravillosamente declarativa y rápida dentro de Xcode, a veces carece de la granularidad visual que teníamos (y seguimos teniendo) en UIKit o AppKit. Aplicar un simple modificador .font() a un Picker estándar a menudo no produce el resultado esperado, especialmente dependiendo del estilo del selector (.wheel, .segmented, .menu) y de la plataforma (iOS, macOS, o watchOS).
En este tutorial exhaustivo (ideal para tu blog tecnológico), vamos a desglosar exactamente por qué ocurre esto y te enseñaré todas las técnicas, desde los “hacks” oficiales usando Appearance Proxies hasta la creación de componentes 100% personalizados y multiplataforma en Swift.
1. ¿Por qué .font() no siempre funciona?
En el mundo ideal de la programación Swift declarativa, el código debería verse así:
Picker("Selecciona un sabor", selection: $sabor) {
Text("Vainilla").tag(0)
Text("Chocolate").tag(1)
}
.font(.custom("MiFuentePersonalizada", size: 18)) // Spoiler: A menudo ignorado
.pickerStyle(.wheel)
Dependiendo de la versión de iOS y del PickerStyle, SwiftUI envuelve los componentes nativos del sistema subyacente (UIPickerView en iOS, NSPopUpButton en macOS, etc.). Estos componentes heredados no siempre escuchan los modificadores de entorno de SwiftUI.
- En estilo .menu (iOS 14+): A veces respeta el modificador de fuente si se aplica directamente al contenido (
Text) dentro del closure, pero no siempre en el botón principal. - En estilo .wheel: Ignora el modificador
.font()por completo en la mayoría de las versiones. - En estilo .segmented: Ignora las fuentes de SwiftUI, ya que depende estrictamente del renderizado de
UISegmentedControl.
Veamos cómo resolver esto plataforma por plataforma y estilo por estilo.
2. Solución en iOS: Integrando UIKit con SwiftUI
Para un iOS Developer, la solución más rápida y robusta para los componentes estándar que se resisten a SwiftUI es volver a las raíces: UIAppearance. Este proxy nos permite cambiar el diseño global de los componentes de UIKit, los cuales SwiftUI utiliza por debajo de la mesa.
2.1 Cambiar la fuente en un WheelPickerStyle (La clásica “rueda”)
El WheelPickerStyle está respaldado por UIPickerView. Para cambiar la fuente del Picker en SwiftUI usando este estilo, necesitamos inyectar nuestra configuración en UIKit.
import SwiftUI
struct WheelPickerPersonalizado: View {
@State private var seleccion = "Opción 1"
let opciones = ["Opción 1", "Opción 2", "Opción 3"]
// Inicializador para configurar el proxy de apariencia
init() {
// Obtenemos la fuente del sistema o una personalizada (ej. Avenir Next)
let customFont = UIFont(name: "AvenirNext-Bold", size: 24) ?? UIFont.systemFont(ofSize: 24)
// Configuramos los atributos de texto
let attributes: [NSAttributedString.Key: Any] = [
.font: customFont,
.foregroundColor: UIColor.systemBlue // También podemos cambiar el color
]
// Aplicamos los atributos a todos los UIPickerView de la app
UIPickerView.appearance().setValue(UIColor.clear, forKey: "magnifyingGlass") // Opcional: quitar lupa
}
var body: some View {
VStack {
Text("Seleccionado: \(seleccion)")
.font(.headline)
// IMPORTANTE: El Text dentro del Picker es lo que UIPickerView renderiza.
// En versiones recientes de iOS, a veces podemos usar .font directamente en el Text,
// pero usar NSAttributedString vía UIKit garantiza soporte retroactivo.
Picker("Opciones", selection: $seleccion) {
ForEach(opciones, id: \.self) { opcion in
Text(opcion)
// Para iOS 15+, aplicar la fuente aquí a veces funciona para Wheel!
.font(.custom("AvenirNext-Bold", size: 24))
.foregroundColor(.blue)
}
}
.pickerStyle(.wheel)
}
.padding()
}
}
Nota para el desarrollador: Si bien
UIPickerView.appearance()es potente, ten cuidado, ya que afecta a todos los Wheel Pickers de tu aplicación. Si necesitas fuentes diferentes para distintos Pickers, tendrás que crear una vistaUIViewRepresentablepersonalizada.
2.2 Cambiar la fuente en un SegmentedPickerStyle
El control segmentado es extremadamente rígido en SwiftUI. Está respaldado por UISegmentedControl. Aquí es imperativo usar UIAppearance.
import SwiftUI
struct SegmentedPickerPersonalizado: View {
@State private var seleccion = 0
init() {
let font = UIFont(name: "Papyrus", size: 16) ?? UIFont.systemFont(ofSize: 16)
// Atributos para el estado normal
let normalAttributes: [NSAttributedString.Key: Any] = [
.font: font,
.foregroundColor: UIColor.darkGray
]
// Atributos para el estado seleccionado
let selectedAttributes: [NSAttributedString.Key: Any] = [
.font: font,
.foregroundColor: UIColor.white
]
UISegmentedControl.appearance().setTitleTextAttributes(normalAttributes, for: .normal)
UISegmentedControl.appearance().setTitleTextAttributes(selectedAttributes, for: .selected)
UISegmentedControl.appearance().selectedSegmentTintColor = .systemIndigo
}
var body: some View {
Picker("Opciones", selection: $seleccion) {
Text("Uno").tag(0)
Text("Dos").tag(1)
Text("Tres").tag(2)
}
.pickerStyle(.segmented)
.padding()
}
}
3. Solución para macOS: Abrazando AppKit
Si tu programación Swift te está llevando al ecosistema de Mac, notarás que macOS usa AppKit en lugar de UIKit. SwiftUI en Mac traduce el Picker a controles como NSPopUpButton o NSRadioGroup.
En macOS, SwiftUI ha mejorado enormemente la capacidad de heredar el modificador .font(). Sin embargo, si necesitas un control absoluto, a veces es necesario envolver un NSPopUpButton. Afortunadamente, para el caso de uso general en macOS moderno (macOS 12+), el menú nativo sí respeta en gran medida el modificador en el contenido.
#if os(macOS)
import SwiftUI
struct MacOSPickerPersonalizado: View {
@State private var seleccion = "Swift"
let lenguajes = ["Swift", "Objective-C", "C++"]
var body: some View {
VStack {
Picker("Lenguaje preferido:", selection: $seleccion) {
ForEach(lenguajes, id: \.self) { lenguaje in
Text(lenguaje)
// En macOS, la fuente aplicada directamente al Text
// suele reflejarse en el menú desplegable.
.font(.custom("Menlo", size: 14))
}
}
.pickerStyle(.menu)
.frame(width: 250)
// Para cambiar la fuente del "Label" o etiqueta del Picker:
.font(.custom("HelveticaNeue-Bold", size: 16))
}
.padding()
}
}
#endif
4. Solución para watchOS: Espacio Limitado, Grandes Decisiones
El Apple Watch, operando con watchOS, tiene un paradigma de interfaz completamente distinto. El selector de tipo rueda se usa a través de la Corona Digital (Digital Crown). En watchOS, los componentes nativos están muy optimizados.
Para cambiar la fuente del Picker en SwiftUI en watchOS, debes aplicar el modificador directamente al contenido.
#if os(watchOS)
import SwiftUI
struct WatchOSPickerPersonalizado: View {
@State private var cantidad = 1
var body: some View {
VStack {
Text("Selecciona cantidad")
.font(.footnote)
Picker("Cantidad", selection: $cantidad) {
ForEach(1...10, id: \.self) { numero in
Text("\(numero)")
// En watchOS, esto cambia el tamaño del texto en la rueda
.font(.system(size: 30, weight: .black, design: .rounded))
.foregroundColor(.green)
}
}
.pickerStyle(.wheel)
}
}
}
#endif
5. El Enfoque Definitivo: Construir un Picker 100% Personalizado en SwiftUI
Si estás cansado de pelear con las limitaciones de los Appearance Proxies, de que UIKit contamine tus vistas declarativas en Xcode, y quieres un control milimétrico multiplataforma, la mejor decisión que puede tomar un iOS Developer es… no usar el Picker nativo de SwiftUI.
Al crear un componente personalizado, garantizamos que el diseño funcione exactamente igual en todas partes, utilizando pura programación Swift.
Vamos a crear un DropdownPicker (estilo Menú) desde cero utilizando DisclosureGroup y ScrollView.
Paso 1: Definir la Vista del Picker Personalizado
import SwiftUI
struct CustomFontPicker<T: Hashable>: View {
let titulo: String
@Binding var seleccion: T
let opciones: [T]
let nombreOpcion: (T) -> String // Closure para convertir genérico a String
// Personalización de fuente
var fuenteTitulo: Font = .headline
var fuenteOpciones: Font = .body
var colorAcento: Color = .blue
@State private var estaExpandido = false
var body: some View {
VStack(alignment: .leading, spacing: 0) {
// Botón principal / Cabecera
Button(action: {
withAnimation(.spring()) {
estaExpandido.toggle()
}
}) {
HStack {
Text(titulo + ": " + nombreOpcion(seleccion))
.font(fuenteTitulo)
.foregroundColor(.primary)
Spacer()
Image(systemName: "chevron.down")
.rotationEffect(.degrees(estaExpandido ? 180 : 0))
.foregroundColor(colorAcento)
.font(fuenteTitulo)
}
.padding()
.background(Color(.systemGray6))
.cornerRadius(estaExpandido ? 0 : 10)
// Esquinas redondeadas condicionales
.clipShape(
RoundedRectangle(cornerRadius: 10)
)
}
// Lista desplegable
if estaExpandido {
ScrollView {
VStack(alignment: .leading, spacing: 0) {
ForEach(opciones, id: \.self) { opcion in
Button(action: {
withAnimation(.spring()) {
seleccion = opcion
estaExpandido = false
}
}) {
HStack {
Text(nombreOpcion(opcion))
.font(fuenteOpciones) // AQUÍ ESTÁ NUESTRO CONTROL TOTAL
.foregroundColor(seleccion == opcion ? colorAcento : .primary)
Spacer()
if seleccion == opcion {
Image(systemName: "checkmark")
.foregroundColor(colorAcento)
}
}
.padding()
.background(Color(.systemBackground))
}
Divider()
}
}
}
.frame(maxHeight: 200) // Limitar altura del scroll
.background(Color(.systemBackground))
.cornerRadius(10)
.shadow(color: Color.black.opacity(0.1), radius: 5, x: 0, y: 5)
}
}
.padding()
}
}
Paso 2: Implementación en tu App
Ahora, usarlo en tu Xcode project es un paseo, y las fuentes responderán sin chistar.
struct ContentView: View {
@State private var lenguajeSeleccionado = "SwiftUI"
let frameworks = ["SwiftUI", "UIKit", "AppKit", "WatchKit"]
var body: some View {
ZStack {
Color(.systemGroupedBackground).edgesIgnoringSafeArea(.all)
VStack {
Text("Panel de Preferencias")
.font(.largeTitle)
.fontWeight(.heavy)
.padding(.bottom, 20)
// Usando nuestro Picker 100% puro SwiftUI
CustomFontPicker(
titulo: "Framework",
seleccion: $lenguajeSeleccionado,
opciones: frameworks,
nombreOpcion: { $0 }, // La opción ya es un String
fuenteTitulo: .custom("Courier", size: 18).bold(),
fuenteOpciones: .custom("Courier", size: 16),
colorAcento: .purple
)
Spacer()
}
}
}
}
¿Las ventajas de este enfoque?
- Sin UIKit: No alteras la apariencia global accidentalmente.
- Totalmente Animable: Puedes cambiar las transiciones de aparición.
- Fuentes sin restricciones: Usa
.custom, cambia el weight, el tracking, lo que necesites.
6. Consideraciones Importantes sobre Dynamic Type y Accesibilidad
Como iOS Developer, tu trabajo no solo consiste en hacer que la app se vea bonita con una fuente genial; también debes asegurar que los usuarios con problemas de visión puedan leerla.
Al cambiar la fuente del Picker en SwiftUI por una fuente personalizada (.custom("NombreFuente", size: X)), debes asegurarte de que la fuente escale con las configuraciones de accesibilidad de iOS (Dynamic Type).
En lugar de poner un tamaño fijo, usa el escalado relativo en SwiftUI:
// Mala práctica: tamaño fijo, rompe la accesibilidad
Text("Opción")
.font(.custom("Avenir", size: 18))
// Buena práctica: escala en relación con el estilo del cuerpo (Body)
Text("Opción")
.font(.custom("Avenir", size: 18, relativeTo: .body))
Si decides utilizar el enfoque de UIAppearance (Paso 2), debes estar preparado para escuchar notificaciones del sistema cuando cambie el tamaño de letra preferido (UIContentSizeCategoryDidChangeNotification) para recalcular tus NSAttributedString, lo cual puede ser tedioso. Esta es otra razón gigantesca por la cual el enfoque del Paso 5 (Picker 100% Personalizado en SwiftUI) es ampliamente superior para aplicaciones de calidad de producción.
7. Tabla Comparativa de Estrategias
Para resumir qué camino debes elegir cuando estés codificando en Xcode:
| Método | Estilo Compatible | Ventajas | Desventajas |
|---|---|---|---|
| .font() Directo | .menu (iOS 15+), .wheel (watchOS) | Rápido, código limpio. | A menudo ignorado en iOS/macOS para componentes principales y .segmented. |
| UIAppearance | .wheel, .segmented (iOS) | Soluciona componentes heredados de UIKit de forma segura. | Afecta globalmente a toda la App; más difícil de integrar con Dynamic Type. |
| UIViewRepresentable | Todos los de UIKit | Control total de instancia. | Requiere mucho código extra (Delegate, DataSource, Coordinator). Pierde la magia declarativa. |
| Vista 100% SwiftUI | N/A (Construyes el tuyo) | Control visual y de fuente absoluto. 100% Declarativo. Multiplataforma real. | Debes programar la lógica de selección, expansión y accesibilidad por tu cuenta. |
Conclusión
El ecosistema de SwiftUI ha madurado enormemente, pero cuando se trata de componentes que actúan como “puentes” hacia APIs antiguas, aún encontramos baches en el camino. Saber cómo cambiar la fuente del Picker en SwiftUI te distingue de ser un simple codificador a ser un verdadero experto iOS Developer en programación Swift.
Ya sea que decidas aplicar “tiritas” mediante UIAppearance para un proyecto rápido, o que inviertas tiempo en diseñar tu propio componente desplegable que respete perfectamente las directrices de tu equipo de diseño, ahora tienes las herramientas en tu arsenal.
Sigue experimentando, sigue leyendo nuestro blog y, sobre todo, diviértete programando en Xcode. La belleza de Swift es que siempre hay múltiples caminos para llegar a una solución elegante.
Si tienes cualquier duda sobre este artículo, contacta conmigo y estaré encantado de ayudarte 🙂. Puedes contactar conmigo en mi perfil de X o en mi perfil de Instagram










