Activity 1
Host a SwiftUI detail
Push a UIHostingController containing a SwiftUI lesson detail from a UIKit course list and pass a completion action back.
Hint: Decide which side owns completion state before building the bridge.
UIKit Development
Adopt frameworks incrementally with hosting controllers, representables, coordinators, and one-way state synchronization.
Before you begin
Learning stage
UIKit and SwiftUI can coexist feature by feature. UIHostingController wraps a SwiftUI hierarchy as a normal view controller that UIKit can push, present, or contain. This makes incremental adoption possible without rebuilding navigation and services at once.
UIViewRepresentable and UIViewControllerRepresentable expose UIKit objects to SwiftUI. The make method creates the object once for an identity; the update method synchronizes it from current SwiftUI state. A Coordinator translates target–action or delegate callbacks back into bindings and closures.
Define one source of truth at the boundary. SwiftUI owns layout for represented objects, so wrapper code should not fight it by assigning frames and transforms. Prevent feedback loops by comparing current values before mutating, clean up observers and delegates, and keep bridge types thin rather than hiding whole feature architectures inside them.
Learning stage
A search-field wrapper sends UIKit editing events into a SwiftUI binding without recreating the field.
import SwiftUI
import UIKit
struct SearchField: UIViewRepresentable {
@Binding var text: String
func makeCoordinator() -> Coordinator { Coordinator(text: $text) }
func makeUIView(context: Context) -> UISearchTextField {
let field = UISearchTextField()
field.placeholder = "Search lessons"
field.addTarget(context.coordinator,
action: #selector(Coordinator.changed(_:)),
for: .editingChanged)
return field
}
func updateUIView(_ field: UISearchTextField, context: Context) {
if field.text != text { field.text = text }
}
final class Coordinator: NSObject {
private var text: Binding<String>
init(text: Binding<String>) { self.text = text }
@objc func changed(_ sender: UISearchTextField) {
text.wrappedValue = sender.text ?? ""
}
}
}Learning stage
Connect the UIKit concept to complete, maintainable application features.
Learning stage
Identify lifecycle, ownership, identity, layout, accessibility, and user-experience risks.
Learning stage
Build the behavior in a small Xcode project and inspect it across realistic states.
Activity 1
Push a UIHostingController containing a SwiftUI lesson detail from a UIKit course list and pass a completion action back.
Hint: Decide which side owns completion state before building the bridge.
Activity 2
Expose an image picker or document controller through UIViewControllerRepresentable and use a coordinator for its delegate.
Hint: Include cancellation and cleanup paths.
Learning stage
Answer each question before revealing the explanation.
QUESTION 1
A UIKit view controller that manages a SwiftUI view hierarchy
UIKit can navigate, present, or contain it like another controller.
QUESTION 2
Synchronize the existing UIKit object with the latest SwiftUI state.
Creation happens in make; subsequent state changes flow through update.
Learning stage
Explain the design in terms of lifecycle, identity, ownership, system behavior, and trade-offs.
Short answer: Use hosting controllers for new SwiftUI screens and representables only where SwiftUI needs existing UIKit components.
I choose feature boundaries, keep one source of truth, pass explicit inputs and events, and preserve existing navigation or services where useful. Coordinators bridge delegate callbacks. I test lifecycle, sizing, dismissal, and state synchronization at every boundary rather than creating a broad hidden dependency layer.
Possible follow-ups
Learning stage
UIKit–SwiftUI interoperability supports incremental architecture when boundaries define ownership, input synchronization, callback flow, layout, and cleanup.