Créer un formulaire interactif

· 4 minutes de lecture
Créer un formulaire interactif

Bonjour :)

Nous avons pu voir dans le tutoriel précédent les bases de SwiftUI, nous avons pu voir certains composants du framework, mais aussi les modificateurs associés à certains composants. Je vous propose aujourd'hui de transformer cette théorie en pratique.

Un générateur de cartes de profil

Notre petit projet permettra à l'utilisateur de saisir son nom ainsi qu'une courte biographie. En validant le formulaire, une carte de profil apparaîtra avec une image aléatoire stylisée, le nom de l'utilisateur et sa bio.

Voici à quoi devra ressembler notre application :)

Je vous conseille fortement d'essayer de trouver des éléments de réponse grâce aux bases de SwiftUI !

🥘 Notre recette :

Ce dont nous avons besoin pour notre application :

  • VStack / HStack / ZStack - organisation de notre vue
  • Text - afficher un contenu
  • TextField - saisir un champ
  • @State pour sauvegarder la valeur saisie dans le TextField
  • Button - pour effectuer des actions
  • AsyncImage ou Image - pour afficher une image
  • Plusieurs modificateurs, ça c'est à vous de décider comment pimper votre app :)

//
//  ContentView.swift
//  MonPremierProjet
//
//  Created by Giovanni  Outerleys on 18/05/2025.
//


import SwiftUI

struct ContentView: View {
    // Variables d'état pour stocker les entrées utilisateur
    @State private var userName: String = ""
    @State private var userBio: String = ""
    @State private var showProfile: Bool = false
    @State private var imageNumber: Int = 1
    
    // Couleurs personnalisées
    let gradientColors = [Color.blue, Color.purple]
    
    var body: some View {
        ScrollView {
            VStack(spacing: 20) {
                // Formulaire
                VStack(alignment: .leading, spacing: 15) {
                    Text("Informations personnelles")
                        .font(.headline)
                    
                    TextField("Votre nom", text: $userName)
                        .padding()
                        .background(Color(.systemGray6))
                        .cornerRadius(8)
                    
                    Text("Biographie")
                        .font(.headline)
                    
                    TextField("Parlez-nous de vous...", text: $userBio)
                        .padding()
                        .frame(height: 100)
                        .background(Color(.systemGray6))
                        .cornerRadius(8)
                    
                    // Bouton pour valider le formulaire
                    Button(action: {
                        // Génère un nombre aléatoire pour l'image (entre 1 et 5)
                        imageNumber = Int.random(in: 1...5)
                        
                        // Affiche la carte de profil
                        withAnimation {
                            showProfile = true
                        }
                    }) {
                        Text("Créer ma carte")
                            .frame(maxWidth: .infinity)
                            .padding()
                            .background(
                                LinearGradient(
                                    colors: gradientColors,
                                    startPoint: .leading,
                                    endPoint: .trailing
                                )
                            )
                            .foregroundColor(.white)
                            .cornerRadius(10)
                            .shadow(radius: 3)
                    }
                    .disabled(userName.isEmpty || userBio.isEmpty)
                    .opacity(userName.isEmpty || userBio.isEmpty ? 0.6 : 1)
                }
                .padding()
                .background(Color.white)
                .cornerRadius(15)
                .shadow(radius: 5)
                .padding()
                
                // Carte de profil (affichée conditionnellement)
                if showProfile {
                    VStack(spacing: 20) {
                        ZStack {
                            // Fond de la carte
                            RoundedRectangle(cornerRadius: 25)
                                .fill(Color.white)
                                .shadow(radius: 10)
                                .frame(maxWidth: .infinity)
                                .padding(.horizontal)
                            
                            VStack(spacing: 15) {
                                // Image de profil
                                ZStack(alignment: .topTrailing) {
                                    // L'URL utiliserait normalement l'ID généré, ici simulé avec imageNumber
                                    AsyncImage(url: URL(string: "https://picsum.photos/500/500?random=\(imageNumber)")) { image in
                                        image
                                            .resizable()
                                            .scaledToFill()
                                            .frame(width: 150, height: 150)
                                            .clipShape(Circle())
                                            .overlay(
                                                Circle()
                                                    .stroke(
                                                        LinearGradient(
                                                            colors: gradientColors,
                                                            startPoint: .leading,
                                                            endPoint: .trailing
                                                        ),
                                                        lineWidth: 4
                                                    )
                                            )
                                            .shadow(radius: 5)
                                    } placeholder: {
                                        ZStack {
                                            Circle()
                                                .fill(Color(.systemGray5))
                                                .frame(width: 150, height: 150)
                                            
                                            ProgressView()
                                        }
                                    }
                                    
                                    // Badge pour effet visuel
                                    Circle()
                                        .fill(
                                            LinearGradient(
                                                colors: gradientColors,
                                                startPoint: .topLeading,
                                                endPoint: .bottomTrailing
                                            )
                                        )
                                        .frame(width: 30, height: 30)
                                        .overlay(
                                            Image(systemName: "checkmark")
                                                .foregroundColor(.white)
                                                .font(.caption.bold())
                                        )
                                }
                                
                                // Nom de l'utilisateur
                                Text(userName)
                                    .font(.title)
                                    .bold()
                                
                                // Biographie
                                Text(userBio)
                                    .font(.body)
                                    .multilineTextAlignment(.center)
                                    .padding(.horizontal)
                                    .foregroundColor(.secondary)
                                
                                // Boutons d'action
                                HStack(spacing: 20) {
                                    Button(action: {
                                        // Simuler une action de partage
                                        print("Partage")
                                    }) {
                                        Label("Partager", systemImage: "square.and.arrow.up")
                                    }
                                    
                                    Button(action: {
                                        // Réinitialiser et retourner au formulaire
                                        withAnimation {
                                            showProfile = false
                                            userName = ""
                                            userBio = ""
                                        }
                                    }) {
                                        Label("Nouvelle carte", systemImage: "arrow.clockwise")
                                    }
                                }
                            }
                            .padding()
                        }
                        .padding(.bottom)
                    }
                    .transition(.opacity)
                }
                
                Spacer()
            }
        }
        .background(Color(.systemGray6).ignoresSafeArea())
    }
}


#Preview {
    ContentView()
}

Explication du code

Notre application utilise plusieurs variables d'état pour gérer dynamiquement l'interface, le contenu étant actualisé dès que ces dernières sont modifiées :

@State private var userName: String = ""
@State private var userBio: String = ""
@State private var showProfile: Bool = false
@State private var imageNumber: Int = 1

Grâce à ces variables, on stocke les entrées utilisateur avec son nom et sa biographie. On contrôle l'affichage conditionnel de la carte de profil. Et on génère une image aléatoire chaque fois qu'une carte est créée.

Le formulaire avec TextField

Contrairement au Text qui permet seulement d'afficher du contenu textuel, le composant TextField permet à l'utilisateur de saisir du texte. Nous l'utilisons avec la liaison bidirectionnelle $ pour mettre à jour nos variables d'état :

TextField("Votre nom", text: $userName)
    .padding()
    .background(Color(.systemGray6))
    .cornerRadius(8)

Organisation avec Stacks

L'interface est organisée hiérachiquement :

  • VStack principal pour l'organisation verticales globale
  • HStack pour les boutons d'action placés côte à côte
  • ZStack pour superposer l'image de profil et le badge de vérification

Affichage conditionnel

La carte de profil n'apparaît que lorsque l'utilisateur valide le formulaire ;) :

if showProfile {
    // Affichage de la carte de profil
}

Stylisation avancée

Vu que je suis un ""designer"", j'utilise plusieurs techniques de stylisation :

  • Dégradés avec LinearGradient pour le titre et les boutons
  • clipShape(Circle()) pour rendre l'image circulaire
  • overlay pour ajouter une bordure dégradée autour de l'image
  • Effets d'ombre avec shadow
  • Animation de transition avec withAnimation

AsyncImage pour charger des images depuis le web

J'ai fait le choix d'utiliser AsyncImage pour charger des images aléatoires, mais tu peux très bien utiliser Image pour afficher une image présente dans ton Assets.

AsyncImage(url: URL(string: "https://picsum.photos/500/500?random=\(imageNumber)")) { image in
    // Stylisation de l'image chargée
} placeholder: {
    // Affichage pendant le chargement
}

Le résultat

Notre simple application démontre la puissance et l'élégance de SwiftUI. Avec relativement peu de code, nous avons pu créer une interface utilisateur interactive.

N'hésitez pas à me faire un retour sur ce que vous avez pu faire !