← Back to Home

How to Display SwiftUI Views from UIViewController

SwiftUI UIKit Integration

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.