TechnologiesSwiftUI

UIViewControllerRepresentable protocol

iOSmacOStvOSwatchOSvisionOS✓ renders

A view that represents a UIKit view controller.

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.

  1. 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 WebController declares struct WebController: UIViewControllerRepresentable, after which it can be used wherever a View is expected — instantiated as WebController() inside the surrounding VStack.

  2. 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 a UIViewController, sets vc.view.backgroundColor = .systemTeal, and returns vc. The associated UIViewControllerType is inferred from this return type.

  3. 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.

  4. Read the bridge through the Context parameter

    Both required methods receive a Context value that exposes the current SwiftUI environment and, when you define one, the type's coordinator for handling UIKit delegation and callbacks. The example accepts context in makeUIViewController(context:) and updateUIViewController(_:context:) without needing the coordinator, but it is the channel through which UIKit events flow back into SwiftUI.

  5. 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 with WebController().frame(height: 120).cornerRadius(12), letting the hosted UIViewController adopt SwiftUI's layout system.

Try it — Change 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.

UIViewControllerRepresentable.swift
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()
    }
}
Live preview
Wrapped UIViewController
Wrapped UIViewController
swift → lexer → parser → sema → uiir → canvas Open in Studio ↗
What's new in SwiftUI 27 →