Skip to content

SwiftUI Performance with UIKit (SwiftUI)

A SwiftUI guide for coding agents. Also covers uihostingconfiguration, uikit cells in swiftui, infinite list performance, uiviewrepresentable table view, hosting controller environment, when to drop to uikit.

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
    }
  }
}

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.

Use this guidance in your coding agent

Install the Better Design MCP once. Your agent then loads this page with one call.

get-swiftui-guide({ topic: "swiftui-swiftui-performance-how-to-use-uikit" })
claude mcp add --scope user better-design --transport http https://better-design.com/api/mcp --header "Authorization: Bearer <YOUR_API_KEY>"
Browse related design systems