Formulario de Notas en SwiftUI: Creación y Personalización
Resumen
Desarrollar formularios permite a los usuarios de tu aplicación introducir y gestionar datos de manera estructurada. Pondremos en práctica los conocimientos adquiridos sobre campos de texto (text fields), editores de texto (text editors), selectores (pickers), y toggles creando un formulario desde cero para añadir notas en nuestra aplicación.
¿Cuál es la base del formulario?
La construcción del formulario comienza con la creación de un nuevo archivo SwiftUI en la carpeta de vistas. Nombramos este archivo como CreateNoteView.swift. La base de nuestro formulario será una combinación de ScrollView y VStack, lo que nos permitirá organizar los elementos de manera vertical y desplazarlos a través de la pantalla si es necesario.
struct CreateNoteView:View{varbody: some View{ScrollView{VStack{// Aquí agregarán los elementos del formulario}}}}
¿Cómo añadir un título?
Para empezar, incorporamos un título visualmente atractivo con propiedades específicas para diferenciarlo del resto de los elementos. Usaremos el texto "Crear nueva nota" como título.
Text("Crear nueva nota").font(.largeTitle).fontWeight(.bold).padding(.bottom,10)
¿Cómo se implementa un TextField?
El siguiente paso es incluir un TextField para permitir que los usuarios inserten el título de la nota. Es esencial que este campo esté conectado a una variable @State que almacenará el texto introducido.
Utilizamos un TextEditor para ofrecer un área amplia donde se puede escribir la descripción o el cuerpo de la nota. Este editor de texto también estará vinculado a una variable @State.
Para dar más interactividad a nuestro formulario, introducimos un Picker que permitirá seleccionar el tamaño de la nota, utilizando una interfaz gráfica sencilla combinada con un HStack para incluir etiquetas descriptivas.
Un Toggle es ideal para ofrecer opciones binarias, como marcar una nota como favorita. Nuevamente, se asocia a una variable @State.
@StateprivatevarisFavorite:Bool=falseToggle("Marcar como favorito",isOn: $isFavorite).padding()
¿Cómo se configura un botón con funcionalidad?
Finalmente, integramos un botón que, al ser presionado, guardará la nueva nota. Este botón estará acompañado por su lógica, llamando a una función que se encargará de procesar y almacenar la información.
Button(action:{onTap()}){Text("Guardar Nota").padding().background(Color.blue).foregroundColor(.white).cornerRadius(8)}.padding()func onTap(){let card =NCard(title: titulo,text: texto,size: size,isFavorite: isFavorite)print("Esta es tu nueva nota: \(card)")// Aquí se puede añadir la lógica para almacenar la nota}
¿Qué pasa al ejecutar el formulario?
Al pasar el cursor sobre el formulario en la vista previa, observamos cómo los elementos interactúan entre ellos. El formulario visualmente se ve atractivo gracias a los modifiers (como padding y background) que mejoran la estética. Cada cambio en las variables de estado se refleja de inmediato, proporcionando una interfaz dinámica y responsiva.
La construcción de un formulario completo en SwiftUI no solo cubre los aspectos visuales, sino también la lógica funcional subyacente, permitiendo que tu aplicación se vea moderna, eficiente y fácil de usar. ¡Sigue explorando SwiftUI para perfeccionar tus habilidades y construir aplicaciones únicas!
El código usando los componentes que habíamos creado en clases anteriores. Aunque tuve que hacerle algunos ajustes.
Si se preguntan como paso la data del View padre hacia los hijos, lo único que hice fue cambiar el valor en el hijo de @State a @Binding
Así quedo previo a ver la clase, me ayudé un poco revisando los archivos hechos con las clases previas
//// NCreateCardView.swift// swiftUIPlatzi2//// Created by Santiago Amaya Cetina on 21/1/26.//importSwiftUIstruct NCreateCardView:View{ @Statevartitle:String="" @StatevarsizeCard:NCardType=.small @Statevartext:String="" @StatevarisFavorite:Bool=false func onTap(){let card =NCard(title: title,text: text,type: sizeCard)print("-> ",card)}varbody: some View{ZStack{LiquidBackground().ignoresSafeArea()ScrollView{VStack{Text("Note's title").font(.title).bold().padding(5)// TextField: "glass" background that blends with the liquid gradient (more professional)TextField("Title",text: $title).font(.headline).foregroundStyle(.white).tint(.white)// cursor color.padding().background(RoundedRectangle(cornerRadius:20,style:.continuous).fill(.ultraThinMaterial)// plays nicely with animated background).overlay(RoundedRectangle(cornerRadius:20,style:.continuous).stroke(.white.opacity(0.25),lineWidth:1)).textFieldStyle(.plain).frame(width:350).shadow(color:.black.opacity(0.18),radius:10,x:0,y:6)TextEditor(text: $text).font(.system(size:16)).foregroundStyle(.white).scrollContentBackground(.hidden).background(LinearGradient(colors:[.pink,.purple],startPoint:.topLeading,endPoint:.bottomTrailing).opacity(0.7)).frame(width:350,height:400).cornerRadius(20).padding().shadow(color:.black.opacity(0.4),radius:5,x:5,y:2)HStack{Text("Size").padding().foregroundStyle(.white)Spacer()Picker("Sizes",selection: $sizeCard){Text("Small").tag(NCardType.small)Text("Medium").tag(NCardType.medium)}// Picker text color (what SwiftUI allows reliably across styles).tint(.white)// affects accent + many picker styles.foregroundStyle(.white)// label color for some styles}Toggle("Is favorite",isOn: $isFavorite).padding().tint(.white)// makes the switch accent fit the design.foregroundStyle(.white)Button{onTap()} label:{Text("Save note").font(.headline).bold().foregroundStyle(.black).frame(maxWidth:300).padding().background(RoundedRectangle(cornerRadius:20,style:.continuous).fill(Color.white)).opacity(0.85)}}}}}}// MARK: - Liquid Background (MeshGradient iOS 17+/macOS 14+)private struct LiquidBackground:View{varbody: some View{Group{if#available(iOS 17.0, macOS 14.0,*){ZStack{LiquidMeshGradient()// Subtle vignette to make content readable + more “pro” lookRadialGradient(colors:[.clear,.black.opacity(0.35)],center:.center,startRadius:40,endRadius:520)}}else{LinearGradient(colors:[Color.blue.opacity(0.85),Color.indigo.opacity(0.75),Color.gray.opacity(0.7)],startPoint:.topLeading,endPoint:.bottomTrailing)}}}}@available(iOS 17.0, macOS 14.0,*)private struct LiquidMeshGradient:View{// Helper outside the ViewBuilder closureprivate func p(_ x:Float, _ y:Float)->SIMD2<Float>{SIMD2<Float>(x, y)}varbody: some View{TimelineView(.animation){ timeline inlet t = timeline.date.timeIntervalSinceReferenceDate// Keep points in the 0...1 range (more stable/professional result)lettopMidX:Float=0.5+0.10*Float(sin(t *0.25))letmidLeftY:Float=0.5+0.12*Float(cos(t *0.22))letmidRightY:Float=0.5+0.10*Float(sin(t *0.20))letbotMidX:Float=0.5+0.08*Float(cos(t *0.18))letpoints:[SIMD2<Float>]=[p(0.0,0.0),p(topMidX,0.0),p(1.0,0.0),p(0.0, midLeftY),p(0.5,0.5),p(1.0, midRightY),p(0.0,1.0),p(botMidX,1.0),p(1.0,1.0)]// Slightly richer palette with controlled contrastletcolors:[Color]=[.blue.opacity(0.95),.cyan.opacity(0.75),.indigo.opacity(0.95),.purple.opacity(0.75),.blue.opacity(0.85),.gray.opacity(0.70),.indigo.opacity(0.85),.blue.opacity(0.75),.gray.opacity(0.65)]MeshGradient(width:3,height:3,points: points,colors: colors).blur(radius:22)// softer transitions (“liquid”).saturation(1.10)// keep color vivid but not neon.contrast(1.05)}}}#Preview{NCreateCardView()}