SwiftUI vs UIKit
Comparativa exhaustiva entre el framework declarativo moderno de Apple, SwiftUI, y el probado en batalla UIKit. ¿Qué framework deberías elegir en 2025?
Model-View-ViewModel: intuitivo, flexible, ampliamente utilizado
The Composable Architecture: funcional, testable, predecible
| Categoría | MVVM | TCA |
|---|---|---|
| Rendimiento | 8/10 | 8/10 |
| Facilidad de aprendizaje | 9/10 | 4/10 |
| Ecosistema | 9/10 | 7/10 |
| Comunidad | 9/10 | 7/10 |
| Mercado laboral | 10/10 | 7/10 |
| A prueba de futuro | 8/10 | 9/10 |
// MVVM - Lista de productos
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 = "No se pudieron cargar los productos: \(error.localizedDescription)"
}
isLoading = false
}
func deleteProduct(_ product: Product) async {
do {
try await repository.delete(product.id)
products.removeAll { $0.id == product.id }
} catch {
errorMessage = "Error al eliminar: \(error.localizedDescription)"
}
}
}
struct ProductListView: View {
@State private var viewModel = ProductListViewModel()
var body: some View {
NavigationStack {
Group {
if viewModel.isLoading {
ProgressView("Cargando...")
} else {
List(viewModel.filteredProducts) { product in
ProductRow(product: product)
}
.searchable(text: $viewModel.searchText)
}
}
.navigationTitle("Productos")
}
.task { await viewModel.loadProducts() }
.alert("Error", isPresented: .constant(viewModel.errorMessage != nil)) {
Button("Aceptar") { viewModel.errorMessage = nil }
} message: {
Text(viewModel.errorMessage ?? "")
}
}
}// TCA - Lista de productos
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("Productos")
}
.task { store.send(.onAppear) }
}
}
}Para proyectos pequeños y medianos, MVVM: desarrollo más rápido y menor curva de entrada. En proyectos grandes, complejos y donde las pruebas son críticas, TCA: el valor de un estado predecible y una arquitectura componible se demuestra a largo plazo. También es posible un enfoque híbrido: funciones principales en MVVM, flujos críticos/complejos en TCA.
Solicita una consultoría gratuitaDe 1 a 2 semanas para entender los conceptos básicos, y de 1 a 2 meses para ser productivo. Te recomendamos revisar los vídeos de Point-Free y el repositorio de ejemplos de TCA.