These are the lesson notes for this day. Its build-along tutorial and flash cards have not been written yet.
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.
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.
makeUIView, updateUIView, and the Coordinator responsible for?“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)
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.