How to Display SwiftUI Views from UIViewController
When migrating from UIKit to SwiftUI or working with a mixed codebase, you'll often need to embed SwiftUI views within existing UIKit view controllers. Here's a comprehensive guide on different approaches to achieve this integration.
1. Using UIHostingController
The simplest way to embed a SwiftUI view in a UIViewController is using UIHostingController:
ProfileView.swift
// SwiftUI View
struct ProfileView: View {
let username: String
var body: some View {
VStack(spacing: 16) {
Image(systemName: "person.circle.fill")
.font(.system(size: 64))
Text(username)
.font(.title)
}
.padding()
}
}
MainViewController.swift
class MainViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Create the SwiftUI view
let profileView = ProfileView(username: "John Doe")
// Create and setup hosting controller
let hostingController = UIHostingController(rootView: profileView)
// Add as child view controller
addChild(hostingController)
view.addSubview(hostingController.view)
hostingController.didMove(toParent: self)
// Setup constraints
hostingController.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
hostingController.view.centerXAnchor.constraint(equalTo: view.centerXAnchor),
hostingController.view.centerYAnchor.constraint(equalTo: view.centerYAnchor),
hostingController.view.widthAnchor.constraint(equalTo: view.widthAnchor),
hostingController.view.heightAnchor.constraint(equalToConstant: 200)
])
}
}
2. Using UIViewControllerRepresentable
For more complex scenarios where you need to handle delegation or data updates:
ProfileHostingController.swift
class ProfileHostingController: UIHostingController<ProfileView> {
var updateCallback: ((String) -> Void)?
override func viewDidLoad() {
super.viewDidLoad()
// Additional setup if needed
view.backgroundColor = .systemBackground
}
}
struct ProfileViewControllerRepresentable: UIViewControllerRepresentable {
let username: String
let onUpdate: (String) -> Void
func makeUIViewController(context: Context) -> ProfileHostingController {
let hostingController = ProfileHostingController(
rootView: ProfileView(username: username)
)
hostingController.updateCallback = onUpdate
return hostingController
}
func updateUIViewController(
_ uiViewController: ProfileHostingController,
context: Context
) {
uiViewController.rootView = ProfileView(username: username)
}
}
3. Using SwiftUI View as a Subview
For simpler cases where you just need to add a SwiftUI view as a subview:
UIView+SwiftUI.swift
extension UIView {
func addSwiftUIView<T: View>(_ view: T, padding: UIEdgeInsets = .zero) {
let hostingController = UIHostingController(rootView: view)
hostingController.view.backgroundColor = .clear
let swiftUIView = hostingController.view!
swiftUIView.translatesAutoresizingMaskIntoConstraints = false
addSubview(swiftUIView)
NSLayoutConstraint.activate([
swiftUIView.topAnchor.constraint(equalTo: topAnchor, constant: padding.top),
swiftUIView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: padding.left),
swiftUIView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -padding.right),
swiftUIView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -padding.bottom)
])
}
}
Usage in a view controller:
ViewController+Usage.swift
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let containerView = UIView()
containerView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(containerView)
// Layout containerView...
// Add SwiftUI view with padding
containerView.addSwiftUIView(
ProfileView(username: "Jane Doe"),
padding: UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16)
)
}
}
Best Practices
- Always use child view controller containment when embedding UIHostingController
- Set proper constraints to manage the SwiftUI view's size
- Consider memory management and lifecycle events
- Use clear background colors to avoid visual glitches
Common Pitfalls
- Not handling view controller lifecycle properly
- Memory leaks from retain cycles
- Incorrect constraint setup leading to layout issues
- Not considering safe area insets
These approaches provide flexible ways to integrate SwiftUI views into your UIKit-based applications. Choose the method that best fits your specific use case and architecture.