Arhitectura Aplicațiilor iOS cu SwiftUI: NavigationStack, Flux Unidirecțional de Date și Gestiunea Stării cu @Observable în Swift (iOS & macOS)

Arhitectură & POP Swift 6.0

Ghid complet de arhitectură enterprise pentru aplicații iOS moderne: implementarea fluxului unidirecțional de date (UDF), navigare tipizată cu NavigationStack și path programmatic, și separarea pe layere cu @Observable.

Sintaxă Swift
NavigationStack(path: $navPath) { ... } .navigationDestination(for: Route.self)

Arhitecturile tradiționale din lumea UIKit (cum ar fi MVC sau VIPER) nu se transpun direct în SwiftUI datorită naturii declarative a acestuia: Interfața utilizator este o funcție pură a stării sale (UI = f(State)). Încercarea de a manipula direct view-urile sau de a folosi controller-e imperative duce rapid la stări inconsistente și bug-uri vizuale greu de reprodus.

Arhitectura modernă recomandată de Apple pentru aplicații mari la scară enterprise este bazată pe Flux Unidirecțional de Date (Unidirectional Data Flow – UDF) combinat cu noul framework Observation și navigare decuplată prin NavigationStack.

Arhitectura Completă a Rutei și Navigării Programatice
import SwiftUI
import Observation

// 1. Rutele aplicației definite ca o enumerare tipizată și Hashable:
enum AppRoute: Hashable {
    case detaliiArticol(id: Int, titlu: String)
    case profilUtilizator(username: String)
    case setariCont
}

// 2. Coordonatorul de Navigare (Router centralizat):
@Observable
@MainActor
final class AppCoordinator {
    var navigationPath = NavigationPath()

    func navigheazaLa(_ ruta: AppRoute) {
        navigationPath.append(ruta)
    }

    func revinoLaRadacina() {
        navigationPath.removeLast(navigationPath.count)
    }
}

// 3. View-ul Rădăcină cu NavigationStack decuplat:
struct MainRootView: View {
    @State private var coordinator = AppCoordinator()

    var body: some View {
        NavigationStack(path: $coordinator.navigationPath) {
            HomeFeedView()
                .navigationTitle("DexBlog Invata")
                // Înregistrarea destinațiilor tipizate:
                .navigationDestination(for: AppRoute.self) { ruta in
                    switch ruta {
                    case .detaliiArticol(let id, let titlu):
                        ArticleDetailView(articleId: id, titlu: titlu)
                    case .profilUtilizator(let user):
                        UserProfileView(username: user)
                    case .setariCont:
                        SettingsView()
                    }
                }
        }
        // Injectăm coordonatorul în mediu pentru acces facil de la orice nivel:
        .environment(coordinator)
    }
}

Ierarhia Corectă a Wrapper-elor de Stare în SwiftUI

Instrument Cine deține starea? Scenariu optim de utilizare
@State View-ul curent (Value Type local). Stare efemeră internă de UI: dacă un popover este deschis, conținutul unui formular în curs de completare, tab-ul activ.
@Binding Un View părinte. Permite unui sub-view copil să citească și să modifice starea deținută de părinte prin legătură bidirecțională (Two-Way Binding).
@Bindable O clasă @Observable. Generează bindings ($model.camp) către proprietățile unui model de date într-un formular sau controale vizuale.
@Environment Arborele de vizualizare (SwiftUI Environment). Injectarea de dependențe globale partajate: stiluri de temă (Light/Dark), instanțe de router, baze de date (SwiftData ModelContext).

Separarea pe Straturi (Clean Architecture)

  • Presentation Layer: Conține structuri View declarate cât mai compacte posibil, fără logici grele de transformare de date.
  • State / ViewModel Layer: Clase adnotate cu @Observable și @MainActor, gestionând doar pregătirea datelor pentru afișare și orchestrarea acțiunilor utilizatorului.
  • Domain / Service Layer: Structuri sau actori puri (ex: AuthenticationService, NetworkClient) independenți de orice framework grafic de UI.