SwiftUI vs UIKit
Comparaison approfondie entre SwiftUI, le framework déclaratif moderne d'Apple, et UIKit, éprouvé de longue date. Quel framework choisir en 2025 ?
Model-View-ViewModel : intuitif, flexible, largement utilisé
The Composable Architecture : fonctionnelle, testable, prévisible
| Catégorie | MVVM | TCA |
|---|---|---|
| Performance | 8/10 | 8/10 |
| Facilité d'apprentissage | 9/10 | 4/10 |
| Écosystème | 9/10 | 7/10 |
| Communauté | 9/10 | 7/10 |
| Marché de l'emploi | 10/10 | 7/10 |
| Pérennité | 8/10 | 9/10 |
// MVVM - Liste de produits
import SwiftUI
import Observation
@Observable
class ProductListViewModel {
var products: [Product] = []
var isLoading = false
var errorMessage: String?
var searchText = ""
private let repository: ProductRepository
init(repository: ProductRepository = .live) {
self.repository = repository
}
var filteredProducts: [Product] {
guard !searchText.isEmpty else { return products }
return products.filter { $0.name.localizedCaseInsensitiveContains(searchText) }
}
func loadProducts() async {
isLoading = true
errorMessage = nil
do {
products = try await repository.fetchProducts()
} catch {
errorMessage = "Échec du chargement des produits : \\(error.localizedDescription)"
}
isLoading = false
}
func deleteProduct(_ product: Product) async {
do {
try await repository.delete(product.id)
products.removeAll { $0.id == product.id }
} catch {
errorMessage = "Échec de la suppression : \\(error.localizedDescription)"
}
}
}
struct ProductListView: View {
@State private var viewModel = ProductListViewModel()
var body: some View {
NavigationStack {
Group {
if viewModel.isLoading {
ProgressView("Chargement...")
} else {
List(viewModel.filteredProducts) { product in
ProductRow(product: product)
}
.searchable(text: $viewModel.searchText)
}
}
.navigationTitle("Produits")
}
.task { await viewModel.loadProducts() }
.alert("Erreur", isPresented: .constant(viewModel.errorMessage != nil)) {
Button("OK") { viewModel.errorMessage = nil }
} message: {
Text(viewModel.errorMessage ?? "")
}
}
}// TCA - Liste de produits
import ComposableArchitecture
import SwiftUI
@Reducer
struct ProductListFeature {
@ObservableState
struct State: Equatable {
var products: [Product] = []
var isLoading = false
var errorMessage: String?
var searchText = ""
var filteredProducts: [Product] {
guard !searchText.isEmpty else { return products }
return products.filter { $0.name.localizedCaseInsensitiveContains(searchText) }
}
}
enum Action {
case onAppear
case searchTextChanged(String)
case deleteProduct(id: String)
case productsLoaded(Result<[Product], Error>)
case productDeleted(Result<Void, Error>)
case dismissError
}
@Dependency(\\.productRepository) var repository
var body: some ReducerOf<Self> {
Reduce { state, action in
switch action {
case .onAppear:
state.isLoading = true
return .run { send in
await send(.productsLoaded(
Result { try await repository.fetchProducts() }
))
}
case .searchTextChanged(let text):
state.searchText = text
return .none
case .deleteProduct(let id):
return .run { send in
await send(.productDeleted(
Result { try await repository.delete(id) }
))
}
case .productsLoaded(.success(let products)):
state.isLoading = false
state.products = products
return .none
case .productsLoaded(.failure(let error)):
state.isLoading = false
state.errorMessage = error.localizedDescription
return .none
case .productDeleted(.success):
return .send(.onAppear)
case .productDeleted(.failure(let error)):
state.errorMessage = error.localizedDescription
return .none
case .dismissError:
state.errorMessage = nil
return .none
}
}
}
}
struct ProductListView: View {
let store: StoreOf<ProductListFeature>
var body: some View {
WithPerceptionTracking {
NavigationStack {
List(store.filteredProducts) { product in
Text(product.name)
}
.searchable(text: store.binding(get: \\.searchText, send: ProductListFeature.Action.searchTextChanged))
.navigationTitle("Produits")
}
.task { store.send(.onAppear) }
}
}
}Pour les projets de petite à moyenne envergure, MVVM — développement plus rapide, barrière d'entrée basse. Pour les projets volumineux, complexes et critiques en matière de tests, TCA — l'état prévisible et l'architecture composable se révèlent payants sur le long terme. Une approche hybride fonctionne aussi : MVVM pour les fonctionnalités clés, TCA pour les flux critiques ou complexes.
Obtenir une consultation gratuite1 à 2 semaines pour saisir les concepts de base, 1 à 2 mois pour devenir productif. Nous recommandons de suivre les vidéos de Point-Free et d'étudier le dépôt d'exemples TCA.