← All daysDay 47 of 49 · Part 11: Platform features and shipping
Day 47

Using SwiftUI with UIKit

These are the lesson notes for this day. Its build-along tutorial and flash cards have not been written yet.

Lesson 54

Using SwiftUI with UIKit

Most real apps use both frameworks. To show SwiftUI inside a UIKit app, wrap a SwiftUI view in a UIHostingController and use it like any other view controller. UIHostingConfiguration puts SwiftUI content inside a table-view or collection-view cell. To use a UIKit view inside SwiftUI, write a UIViewRepresentable: makeUIView creates the view once; updateUIView applies new state every time SwiftUI updates; and a Coordinator acts as the delegate that sends events back.

Since iOS 26, UIKit observes @Observable models automatically, so both frameworks can share a single model. The usual way to adopt SwiftUI is gradual: new screens in SwiftUI, existing screens left as they are, and shared models underneath both.

Example
final class TripsViewController: UIViewController {
    func showDetail(for tripID: Int) {
        // A SwiftUI view, used as an ordinary view controller.
        let detail = UIHostingController(rootView: TripDetail(tripID: tripID))
        navigationController?.pushViewController(detail, animated: true)
    }
}
Exercise

Add one SwiftUI screen to an existing UIKit app using UIHostingController. Then wrap one UIKit view — a map or a text view — for use in SwiftUI, with a two-way binding.

After this lesson you should be able to answer
  1. How do you present a SwiftUI view from UIKit?
  2. What is each of makeUIView, updateUIView, and the Coordinator responsible for?
  3. How can UIKit and SwiftUI share the same data?
Resources
Write the code
Interview problem · Part 11

Wrapping a UIKit view for SwiftUI

“We have a well-tested UIKit text view that we are not going to rewrite. Wrap it for SwiftUI with a two-way binding to its text. Then tell me the two bugs most people ship in a wrapper like this.”

Where this comes from: WWDC26: Use SwiftUI with AppKit and UIKit · Senior iOS Interview Preparation (GitHub)

Show the answer
struct LegacyTextView: UIViewRepresentable {
    @Binding var text: String

    func makeCoordinator() -> Coordinator { Coordinator(text: $text) }

    func makeUIView(context: Context) -> UITextView {       // called ONCE: create & wire
        let view = UITextView()
        view.delegate = context.coordinator
        return view
    }

    func updateUIView(_ view: UITextView, context: Context) {   // called OFTEN: push state in
        context.coordinator.text = $text
        if view.text != text { view.text = text }
    }

    final class Coordinator: NSObject, UITextViewDelegate {
        var text: Binding<String>
        init(text: Binding<String>) { self.text = text }

        func textViewDidChange(_ textView: UITextView) {
            text.wrappedValue = textView.text
        }
    }
}

Bug one: assigning view.text unconditionally in updateUIView. That method runs on every SwiftUI update, so each keystroke would reset the cursor and could echo back through the delegate. The equality check prevents it. Bug two: a coordinator holding an out-of-date binding. Refresh it in updateUIView, as above, so that it always writes to the current one.

The shape to remember: make runs once and creates the view. update runs often and pushes state into it. The Coordinator is the delegate that carries events back out. It is the delegate pattern you already know, with SwiftUI as the owner.

What interviewers listen for: Create once and update often, and the feedback loop named before it causes trouble. That is what someone who has shipped this sounds like.