How do you use UIKit in SwiftUI and SwiftUI in UIKit?
MediumAsked oftenSwiftUIUIKit 2 min read
TL;DR
- UIKit inside SwiftUI:
UIViewRepresentablefor views,UIViewControllerRepresentablefor view controllers. Use aCoordinatorfor delegates. - SwiftUI inside UIKit:
UIHostingControllerfor screens or parts of screens,UIHostingConfigurationfor cells (iOS 16).
Most real apps mix both, so interviewers ask this often.
UIKit inside SwiftUI
struct SearchBar: UIViewRepresentable {
@Binding var text: String
func makeUIView(context: Context) -> UISearchBar {
let bar = UISearchBar()
bar.delegate = context.coordinator
return bar
}
func updateUIView(_ bar: UISearchBar, context: Context) {
if bar.text != text { bar.text = text }
}
func makeCoordinator() -> Coordinator { Coordinator(text: $text) }
final class Coordinator: NSObject, UISearchBarDelegate {
var text: Binding<String>
init(text: Binding<String>) { self.text = text }
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
text.wrappedValue = searchText
}
}
}| Method | When it runs | What to do |
|---|---|---|
makeUIView | Once | Create and configure the view |
updateUIView | Every time SwiftUI state changes | Push new values into the view |
makeCoordinator | Once, before makeUIView | Create the delegate / target object |
dismantleUIView | When removed | Clean up observers |
sizeThatFits (iOS 16) | During layout | Give a custom size |
Rules:
- Do not create the view in
updateUIView. Only update it. - Compare before you set (
if bar.text != text). This avoids loops and cursor jumps. - The coordinator sends UIKit events back to SwiftUI, usually through a
Binding.
A view controller
struct DocumentPicker: UIViewControllerRepresentable {
var onPick: (URL) -> Void
func makeUIViewController(context: Context) -> UIDocumentPickerViewController {
let picker = UIDocumentPickerViewController(forOpeningContentTypes: [.pdf])
picker.delegate = context.coordinator
return picker
}
func updateUIViewController(_ vc: UIDocumentPickerViewController, context: Context) {}
func makeCoordinator() -> Coordinator { Coordinator(onPick: onPick) }
final class Coordinator: NSObject, UIDocumentPickerDelegate {
let onPick: (URL) -> Void
init(onPick: @escaping (URL) -> Void) { self.onPick = onPick }
func documentPicker(_ c: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
urls.first.map(onPick)
}
}
}SwiftUI inside UIKit
let settings = UIHostingController(rootView: SettingsView())
navigationController?.pushViewController(settings, animated: true)To embed it as a child:
let host = UIHostingController(rootView: BannerView(title: "Sale"))
addChild(host)
view.addSubview(host.view)
host.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
host.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
host.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
host.view.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor)
])
host.didMove(toParent: self)
host.sizingOptions = .intrinsicContentSize // iOS 16: size follows SwiftUI contentDo not forget addChild and didMove(toParent:). Without them, appearance callbacks and environment values break.
SwiftUI in cells
cell.contentConfiguration = UIHostingConfiguration {
HStack {
Image(systemName: "star")
Text(item.title)
}
}
.margins(.all, 12)This is great for migrating a list screen step by step.
Passing data into a hosted view
The rootView is a value. To update it, set a new rootView, or share an @Observable model:
let model = BannerModel()
let host = UIHostingController(rootView: BannerView(model: model))
model.title = "New title" // view updates automaticallyNewer helpers
- iOS 18:
UIGestureRecognizerRepresentablewraps UIKit gesture recognizers for SwiftUI. - iOS 18: UIKit can animate with SwiftUI
Animationvalues (UIView.animate(_ animation:)). - iOS 26: UIKit tracks
@Observablemodels automatically. Read a property inlayoutSubviewsor the newupdateProperties(), and UIKit updates the view when it changes.
Common follow-up questions
- Why use a Coordinator instead of the struct itself? The struct is recreated often. Delegates need a stable class instance.
- How do you get the environment in
updateUIView? Usecontext.environment. - Can I call SwiftUI
dismissfrom UIKit? Pass a closure in, or use theCoordinatorto call back. - What breaks most often? Missing
addChild, sizing of hosted views, and update loops inupdateUIView.