cat swift/uikit-appkit-quando-servono.md
UIKit e AppKit quando servono davvero
Capire quando integrare UIKit o AppKit in un progetto SwiftUI e come farlo senza contaminare dominio e casi d'uso.
SwiftUI è il default ragionevole per molte nuove interfacce Apple. Ma UIKit e AppKit non sono spariti.
Servono ancora quando:
- una API esiste solo in UIKit/AppKit;
- hai componenti legacy;
- devi integrare un controller complesso;
- serve controllo fine non ancora comodo in SwiftUI;
- stai migrando una app esistente;
- devi incorporare viste native specifiche.
La regola del corso: UIKit e AppKit sono adattatori di interfaccia, non luoghi dove mettere dominio.
Integrare UIKit in SwiftUI
Per incorporare una view UIKit:
import SwiftUI
import UIKit
struct CameraPreview: UIViewRepresentable {
func makeUIView(context: Context) -> PreviewView {
PreviewView()
}
func updateUIView(_ uiView: PreviewView, context: Context) {
}
}
Per un controller:
struct DocumentScannerView: UIViewControllerRepresentable {
let onScan: ([ScannedPage]) -> Void
func makeUIViewController(context: Context) -> ScannerViewController {
let controller = ScannerViewController()
controller.delegate = context.coordinator
return controller
}
func updateUIViewController(_ controller: ScannerViewController, context: Context) {
}
func makeCoordinator() -> Coordinator {
Coordinator(onScan: onScan)
}
final class Coordinator: NSObject, ScannerDelegate {
let onScan: ([ScannedPage]) -> Void
init(onScan: @escaping ([ScannedPage]) -> Void) {
self.onScan = onScan
}
func scannerDidFinish(pages: [ScannedPage]) {
onScan(pages)
}
}
}
Il coordinator traduce delegate UIKit in evento SwiftUI.
Integrare SwiftUI in UIKit
In app legacy, puoi portare SwiftUI dentro UIKit:
let view = ProductDetailScreen(viewModel: viewModel)
let controller = UIHostingController(rootView: view)
navigationController?.pushViewController(controller, animated: true)
Questo permette migrazioni progressive.
Non devi riscrivere tutta l’app per usare SwiftUI in una feature nuova.
AppKit
Su macOS, l’integrazione segue lo stesso principio:
struct LegacyTableView: NSViewRepresentable {
func makeNSView(context: Context) -> NSTableView {
NSTableView()
}
func updateNSView(_ tableView: NSTableView, context: Context) {
}
}
AppKit resta importante per:
- menu avanzati;
- finestre personalizzate;
- documenti;
- drag and drop complesso;
- integrazioni storiche macOS;
- controlli molto specifici.
Dove mettere il codice bridge
Organizzazione consigliata:
Presentation/
SwiftUI/
UIKitAdapters/
AppKitAdapters/
Application/
UseCases/
Domain/
Models/
Il bridge non deve importare infrastruttura se non serve. Deve parlare con view model o callback.
Evitare domain leakage
Questo è un cattivo segnale:
final class CheckoutViewController: UIViewController {
func pay() {
let dto = PaymentDTO(...)
let url = URL(string: "https://api.example.com/pay")!
...
}
}
Il controller sta facendo UI, DTO, networking e dominio.
Meglio:
@MainActor
final class CheckoutViewModel {
private let checkout: CheckoutUseCase
func pay() async {
...
}
}
Il controller o la view SwiftUI chiama il view model.
Delegate e async
Molte API UIKit/AppKit sono basate su delegate. Puoi adattarle a closure o async sequence.
Closure:
final class PickerCoordinator: NSObject, UIImagePickerControllerDelegate {
let onImage: (UIImage) -> Void
init(onImage: @escaping (UIImage) -> Void) {
self.onImage = onImage
}
}
Async stream concettuale:
struct SelectionEvents {
let stream: AsyncStream<Selection>
}
L’obiettivo è non far trapelare delegate ovunque.
Thread e MainActor
UIKit e AppKit sono UI framework. Gli aggiornamenti devono stare sul main actor.
@MainActor
func showResult(_ result: ScanResult) {
state = .loaded(result)
}
Non passare oggetti UI dentro task concorrenti. Estrai dati, poi inviali al dominio.
Quando usare ancora UIKit puro
UIKit puro ha senso quando:
- il team ha una app grande già UIKit;
- serve compatibilità con componenti esistenti;
- la schermata è costruita intorno a un controller legacy;
- SwiftUI introduce workaround peggiori del problema;
- stai facendo manutenzione evolutiva.
Il corso non deve trasformare UIKit in un nemico. Deve insegnare giudizio.
Checklist
Integra UIKit/AppKit quando:
- esiste un bisogno reale;
- il bridge è isolato;
- dominio e casi d’uso restano fuori;
- delegate vengono tradotti in eventi chiari;
- aggiornamenti UI stanno sul main actor;
- la migrazione può essere progressiva;
- il codice legacy non detta la forma del dominio.
Esercizio
Prendi una feature SwiftUI e immagina di dover usare un componente UIKit.
Costruisci:
- un wrapper
UIViewControllerRepresentable; - un
Coordinator; - una closure di output;
- un view model che riceve l’evento;
- un caso d’uso chiamato dal view model.
Obiettivo: il componente UIKit deve restare al bordo. Il dominio non deve sapere che UIKit esiste.