iOS development
SwiftUI
UIKit
app integration
mobile app development

Include SwiftUI views in existing UIKit application

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

The safest way to adopt SwiftUI in an older UIKit app is incrementally. UIHostingController lets a SwiftUI view behave like a normal UIKit view controller, so you can add new screens or components without rewriting the whole application shell.

Present a SwiftUI screen from UIKit

The simplest integration point is a full-screen push or modal presentation. Build a SwiftUI view, wrap it in UIHostingController, and present it like any other UIKit controller.

swift
1import SwiftUI
2
3struct ProfileView: View {
4    let username: String
5
6    var body: some View {
7        VStack(spacing: 12) {
8            Text("Profile")
9                .font(.headline)
10            Text(username)
11                .font(.title2)
12        }
13        .padding()
14    }
15}
swift
1import UIKit
2import SwiftUI
3
4final class HomeViewController: UIViewController {
5    override func viewDidLoad() {
6        super.viewDidLoad()
7        view.backgroundColor = .systemBackground
8    }
9
10    @IBAction func showProfile() {
11        let host = UIHostingController(rootView: ProfileView(username: "mark"))
12        navigationController?.pushViewController(host, animated: true)
13    }
14}

This works well for leaf screens where UIKit still owns navigation and app lifecycle.

Embed SwiftUI as one part of a UIKit screen

You do not need a full-screen transition to use SwiftUI. A hosting controller can also be added as a child controller inside an existing UIKit layout.

swift
1import UIKit
2import SwiftUI
3
4final class DashboardViewController: UIViewController {
5    private let host = UIHostingController(
6        rootView: ProfileView(username: "mark")
7    )
8
9    override func viewDidLoad() {
10        super.viewDidLoad()
11        view.backgroundColor = .systemBackground
12
13        addChild(host)
14        host.view.translatesAutoresizingMaskIntoConstraints = false
15        view.addSubview(host.view)
16
17        NSLayoutConstraint.activate([
18            host.view.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
19            host.view.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
20            host.view.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 16)
21        ])
22
23        host.didMove(toParent: self)
24    }
25}

That pattern is useful for dashboards, settings pages, and card-style components that benefit from SwiftUI layout while UIKit still controls the surrounding screen.

Pass data from UIKit into SwiftUI

For immutable input, pass values directly through the SwiftUI view initializer. For shared mutable state, use an observable object and provide it to the view.

swift
1import SwiftUI
2import Combine
3
4final class SettingsStore: ObservableObject {
5    @Published var notificationsEnabled = true
6}
7
8struct SettingsView: View {
9    @ObservedObject var store: SettingsStore
10
11    var body: some View {
12        Toggle("Notifications", isOn: $store.notificationsEnabled)
13            .padding()
14    }
15}
swift
let store = SettingsStore()
let host = UIHostingController(rootView: SettingsView(store: store))

UIKit remains the owner of the shared object, while SwiftUI reacts to its published changes.

Send events back to UIKit

SwiftUI views can report user actions through closures, which is often simpler than building a delegate protocol for small integrations.

swift
1import SwiftUI
2
3struct ActionPanel: View {
4    let onDone: () -> Void
5
6    var body: some View {
7        Button("Done", action: onDone)
8            .padding()
9    }
10}
swift
1let host = UIHostingController(
2    rootView: ActionPanel {
3        print("SwiftUI action completed")
4    }
5)

This keeps the ownership boundary clean: SwiftUI renders the interface, and UIKit decides what the action means inside the larger app flow.

Plan the migration boundary deliberately

Hybrid apps become messy when both frameworks try to own the same concern. A practical division is:

  • UIKit owns navigation stacks, tab bars, and app lifecycle
  • SwiftUI owns new leaf screens or isolated reusable components
  • shared state lives in framework-neutral models or observable objects

That approach lets you modernize screen by screen without creating two competing app architectures.

Common Pitfalls

The most common mistake is forgetting proper child-controller containment when embedding SwiftUI in part of a UIKit screen. If you call addSubview without addChild and didMove, lifecycle behavior becomes inconsistent.

Another issue is passing mutable data by plain value when the SwiftUI view needs to react to updates. For dynamic state, use ObservableObject, bindings, or another explicit shared model.

Developers also underestimate lifecycle differences. onAppear in SwiftUI is related to view rendering, not a perfect one-to-one replacement for every UIKit appearance callback, so do not assume they fire under identical conditions.

Finally, avoid migrating navigation and presentation logic too early. Let UIKit continue owning the application shell until there is a clear reason to move more of the stack.

Summary

  • Use UIHostingController to present or embed SwiftUI inside an existing UIKit app.
  • Push hosted views as normal controllers or add them as child controllers inside UIKit layouts.
  • Pass data through initializers or observable objects, and send actions back with closures.
  • Keep ownership boundaries clear so UIKit and SwiftUI do not compete for the same responsibilities.
  • Adopt SwiftUI incrementally rather than rewriting the application in one pass.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.