En el ecosistema actual de desarrollo de aplicaciones móviles, la usabilidad y la jerarquía visual son fundamentales. Para cualquier iOS Developer que busque destacar, dominar los patrones de diseño modernos es una obligación. Uno de los elementos más reconocibles y útiles es el Botón de Acción Flotante (FAB, por sus siglas en inglés: Floating Action Button).
Aunque originalmente popularizado por Material Design, el FAB ha encontrado su hogar en el ecosistema de Apple, facilitando acciones primarias como redactar un correo, iniciar un tweet o agregar una tarea. En este tutorial exhaustivo, aprenderás paso a paso cómo crear un botón de acción flotante en SwiftUI, optimizando tu flujo de trabajo en Xcode y aplicando las mejores prácticas de la programación Swift.
¿Por qué usar un FAB en SwiftUI?
Antes de escribir código, entendamos la teoría. Un FAB es un botón circular que flota sobre el contenido de la interfaz. Su objetivo es promover la acción principal de la pantalla. Al desarrollar en SwiftUI, la implementación difiere drásticamente de UIKit; aquí dejamos atrás los constraints complejos para abrazar el poder de los Stacks y los Overlays.
Parte 1: Implementación Básica con ZStack
Para empezar en Xcode, necesitamos comprender el contenedor ZStack. A diferencia de las pilas verticales u horizontales, ZStack nos permite superponer vistas en el eje de profundidad (Z). La estrategia clásica para un desarrollador principiante suele ser usar Spacers para empujar el botón a una esquina.
Veamos el primer ejemplo de código básico:
import SwiftUI
struct BasicFABView: View {
var body: some View {
ZStack {
// Capa 1: El Contenido Principal (Lista de ejemplo)
NavigationView {
List(0..<30) { item in
Text("Elemento de lista #\(item)")
.padding()
}
.navigationTitle("Inicio")
}
// Capa 2: El Botón Flotante (FAB)
VStack {
Spacer() // Empuja todo hacia el fondo
HStack {
Spacer() // Empuja todo hacia la derecha
Button(action: {
print("FAB presionado: Acción principal")
}) {
Image(systemName: "plus")
.font(.title.weight(.semibold))
.padding()
.background(Color.blue)
.foregroundColor(.white)
.clipShape(Circle())
.shadow(radius: 4, x: 0, y: 4)
}
.padding() // Margen respecto al borde seguro de la pantalla
}
}
}
}
}
Este enfoque funciona, pero como iOS Developer experto, notarás que llenar la vista de Spacers puede ensuciar el código y dificultar la lectura, especialmente en vistas complejas.
Parte 2: La forma profesional usando .overlay
Una técnica mucho más limpia y potente en la programación Swift moderna es utilizar el modificador .overlay() junto con la propiedad de alineación. Esto vincula el botón al contenedor padre sin necesidad de estructuras ZStack adicionales que envuelvan toda la pantalla.
struct ProfessionalFABView: View {
var body: some View {
NavigationView {
List(0..<20) { i in
Text("Tarea pendiente \(i)")
}
.navigationTitle("Mis Tareas")
// Aquí aplicamos la magia de SwiftUI
.overlay(
Button(action: {
// Lógica de creación aquí
}) {
Image(systemName: "plus")
.font(.title2)
.frame(width: 60, height: 60) // Tamaño fijo para el área táctil
.background(Color.accentColor)
.foregroundColor(.white)
.clipShape(Circle())
.shadow(color: .black.opacity(0.3), radius: 5, x: 3, y: 3)
}
.padding() // Padding para separar del borde
, alignment: .bottomTrailing // Alineación automática
)
}
}
}
Parte 3: Creando un Componente Reutilizable
El principio DRY (Don’t Repeat Yourself) es vital. Si vas a crear un botón de acción flotante en SwiftUI en múltiples pantallas, lo ideal es encapsularlo en una estructura reutilizable. Esto te permitirá cambiar el diseño en un solo lugar y que se refleje en toda tu app.
import SwiftUI
struct FloatingActionButton: View {
// Propiedades configurables para máxima flexibilidad
var icon: String = "plus"
var backgroundColor: Color = .blue
var foregroundColor: Color = .white
var action: () -> Void
var body: some View {
Button(action: action) {
Image(systemName: icon)
.font(.system(size: 24))
.foregroundColor(foregroundColor)
.frame(width: 60, height: 60)
.background(backgroundColor)
.clipShape(Circle())
.shadow(color: Color.black.opacity(0.3), radius: 5, x: 3, y: 3)
.contentShape(Circle()) // Mejora la zona de toque
}
.padding()
}
}
Parte 4: FAB Expandible (Menú Animado)
Llevemos esto al siguiente nivel. A menudo, un solo botón no es suficiente. Necesitamos un menú desplegable (tipo “Speed Dial”). Aquí es donde SwiftUI brilla con sus animaciones declarativas.
Utilizaremos @State para controlar la visibilidad y withAnimation para suavizar la transición.
struct ExpandableFAB: View {
@State private var isExpanded = false
var body: some View {
ZStack(alignment: .bottomTrailing) {
// Fondo simulado
Color.gray.opacity(0.1).ignoresSafeArea()
VStack(spacing: 20) {
if isExpanded {
// Botones de acción secundaria
menuButton(icon: "camera.fill", label: "Cámara") { print("Cámara") }
menuButton(icon: "photo.fill", label: "Galería") { print("Galería") }
}
// Botón Principal
Button(action: {
withAnimation(.spring(response: 0.4, dampingFraction: 0.6)) {
isExpanded.toggle()
}
}) {
Image(systemName: "plus")
.font(.title2)
.rotationEffect(.degrees(isExpanded ? 45 : 0)) // Rota a una 'X'
.foregroundColor(.white)
.frame(width: 60, height: 60)
.background(Color.blue)
.clipShape(Circle())
.shadow(radius: 5)
}
}
.padding()
}
}
// Helper para botones pequeños
func menuButton(icon: String, label: String, action: @escaping () -> Void) -> some View {
HStack {
Text(label)
.font(.caption)
.padding(5)
.background(Color.white)
.cornerRadius(5)
.shadow(radius: 2)
Button(action: action) {
Image(systemName: icon)
.padding()
.background(Color.white)
.foregroundColor(.blue)
.clipShape(Circle())
.shadow(radius: 3)
}
}
.transition(.scale.combined(with: .move(edge: .bottom))) // Transición combinada
}
}
Parte 5: Adaptación Multiplataforma (macOS y watchOS)
Un verdadero iOS Developer sabe que el código de SwiftUI puede correr en múltiples dispositivos, pero la experiencia de usuario (UX) debe adaptarse. Un botón gigante en la esquina no funciona igual en un Mac que en un reloj.
Adaptación para macOS
En macOS, debemos manejar el estado hover (cuando el ratón pasa por encima) y eliminar los estilos de botón predeterminados que añaden bordes grises.
#if os(macOS)
struct MacFAB: View {
@State private var isHovering = false
var body: some View {
ZStack(alignment: .bottomTrailing) {
Color.white // Contenido
Button(action: { print("Acción en Mac") }) {
Image(systemName: "plus")
.font(.title)
.foregroundColor(.white)
.frame(width: 50, height: 50)
}
.buttonStyle(.plain) // CRUCIAL en macOS
.background(isHovering ? Color.blue.opacity(0.8) : Color.blue)
.clipShape(Circle())
.shadow(radius: 4)
.padding(20)
.onHover { hovering in
withAnimation(.easeInOut(duration: 0.2)) {
isHovering = hovering
}
}
}
.frame(minWidth: 400, minHeight: 300)
}
}
#endif
Adaptación para watchOS
En el Apple Watch, las esquinas son peligrosas y el espacio es limitado. A menudo es mejor colocar el botón al final de un ScrollView o usar la .toolbar, pero si necesitas un botón flotante, asegúrate de que no tape contenido.
struct WatchFAB: View {
var body: some View {
ZStack {
List(0..<10) { _ in Text("Datos...") }
VStack {
Spacer()
Button(action: {}) {
Image(systemName: "mic.fill")
.font(.title3)
}
.frame(width: 50, height: 50)
.background(Color.red)
.clipShape(Circle())
.buttonStyle(.plain) // Evita el estilo de lista completa
.padding(.bottom, 5)
}
}
}
}
Accesibilidad: El toque final del profesional
No olvides la accesibilidad. Un botón que solo contiene un icono puede ser invisible para usuarios que utilizan VoiceOver. En Xcode, puedes probar esto fácilmente con el inspector de accesibilidad.
Button(action: {
// Acción
}) {
Image(systemName: "plus")
}
.accessibilityLabel("Crear nueva nota") // Lo que lee VoiceOver
.accessibilityHint("Abre el editor para escribir una nota nueva") // Ayuda adicional
.accessibilityAddTraits(.isButton)
Conclusión
Saber cómo crear un botón de acción flotante en SwiftUI es una habilidad esencial que combina diseño y funcionalidad. Hemos recorrido desde la implementación básica con ZStack hasta soluciones avanzadas con menús animados y soporte multiplataforma.
Como iOS Developer, tu objetivo siempre debe ser escribir código limpio y mantenible. Utilizar modificadores como .overlay y extraer componentes reutilizables no solo mejora la legibilidad de tu proyecto en Xcode, sino que facilita la escalabilidad futura de tus aplicaciones. ¡Ahora es tu turno de implementar estos conceptos en tu próximo gran proyecto!
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








