Build the app core, lifecycle and navigation, in SwiftUI, and drop to UIKit only for the
performance-critical surfaces such as infinite feeds. UIHostingConfiguration puts SwiftUI
views inside recycled UITableView or UICollectionView cells.
Why not a UIKit shell anymore
The old pattern, a UIKit coordinator pushing UIHostingController-wrapped screens, still works
and suits apps that support iOS 15 and earlier. Its cost: every UIHostingController creates
its own SwiftUI environment, so environment values, styling, and shared data do not propagate
between screens. From iOS 16, NavigationStack with a NavigationPath covers deep linking and
state restoration in pure SwiftUI.
@main
struct ExamplesApp: App {
@State private var path = NavigationPath()
@State private var store = Store()
var body: some Scene {
WindowGroup {
NavigationStack(path: $path) {
HugeListView(items: store.items)
.navigationDestination(for: Item.self) { item in /* details */ }
.onOpenURL { url in /* parse and push */ }
}
}
}
}
UIHostingConfiguration for huge lists
Where SwiftUI lists glitch, social timelines, calendar layouts, any effectively infinite
collection, wrap a UITableView in a UIViewRepresentable and keep cell dequeuing. Each cell's
contentConfiguration hosts a SwiftUI view.
struct HugeListView: UIViewRepresentable {
let items: [Item]
func makeUIView(context: Context) -> UITableView {
let tableView = UITableView()
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
tableView.dataSource = context.coordinator
return tableView
}
func updateUIView(_ uiView: UITableView, context: Context) { uiView.reloadData() }
func makeCoordinator() -> Coordinator { Coordinator(items: items) }
final class Coordinator: NSObject, UITableViewDataSource {
let items: [Item]
init(items: [Item]) { self.items = items }
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.contentConfiguration = UIHostingConfiguration {
ItemView(item: items[indexPath.row])
}
return cell
}
}
}
Gotcha: no NavigationLink inside cells
UIHostingConfiguration exists only for embedding SwiftUI in UICollectionView and
UITableView cells. Do not put a NavigationLink inside one: handle selection in the
coordinator and append the value to the NavigationStack path instead.