How it works
Use a UIViewControllerRepresentable to wrap a UIKit view controller so that it can participate in a SwiftUI view hierarchy. SwiftUI does not have a native counterpart for every UIKit view controller, so when you need a screen built around UIViewController — a web view, a media player, an image picker, or your own custom controller — you adopt this protocol to bridge it across. The conforming type behaves like any other SwiftUI View: you place it in a layout, apply modifiers, and SwiftUI manages its lifetime, while you remain responsible for creating the controller and keeping it in sync with SwiftUI state.
Conform a wrapper type to UIViewControllerRepresentable
Adopting the protocol turns an ordinary struct into a SwiftUI view that stands in for a UIKit controller. Here
WebControllerdeclaresstruct WebController: UIViewControllerRepresentable, after which it can be used wherever aViewis expected — instantiated asWebController()inside the surroundingVStack.Create the controller in makeUIViewController(context:)
SwiftUI calls this method once to instantiate the view controller it will display. Return the configured controller you want SwiftUI to host; in
makeUIViewController(context:)the example builds aUIViewController, setsvc.view.backgroundColor = .systemTeal, and returnsvc. The associatedUIViewControllerTypeis inferred from this return type.Refresh state in updateUIViewController(_:context:)
Whenever relevant SwiftUI state changes, SwiftUI calls this method so you can push new data into the controller you created. It hands you the same instance back as its first parameter;
updateUIViewController(_ vc: UIViewController, context:)is left empty here because the wrapped controller holds no SwiftUI-driven state, but this is where you would apply updates.Read the bridge through the Context parameter
Both required methods receive a
Contextvalue that exposes the current SwiftUI environment and, when you define one, the type's coordinator for handling UIKit delegation and callbacks. The example acceptscontextinmakeUIViewController(context:)andupdateUIViewController(_:context:)without needing the coordinator, but it is the channel through which UIKit events flow back into SwiftUI.Lay out and style the wrapper like any View
Because the conforming type is a SwiftUI
View, it composes with standard layout and modifiers rather than UIKit sizing rules. The example sizes and shapes the bridged controller withWebController().frame(height: 120).cornerRadius(12), letting the hostedUIViewControlleradopt SwiftUI's layout system.
vc.view.backgroundColor = .systemTeal to .systemPink and rerun to confirm that the live UIKit controller returned from makeUIViewController(context:) is what SwiftUI renders inside the .frame(height: 120).Example & preview
Press Run live & edit to compile it in your browser — then edit the Swift on the left and the preview re-renders live.
struct UIViewControllerRepresentableDemo: View {
struct WebController: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> UIViewController {
let vc = UIViewController()
vc.view.backgroundColor = .systemTeal
return vc
}
func updateUIViewController(_ vc: UIViewController, context: Context) {}
}
var body: some View {
VStack(spacing: 12) {
Text("Wrapped UIViewController")
.font(.headline)
WebController()
.frame(height: 120)
.cornerRadius(12)
}
.padding()
}
}