How do UIViewRepresentable and its coordinator work, and when does SwiftUI call each method?
Predict first
How many times are make and update called after the first render, and after one state change?
import UIKit
import SwiftUI
var makes = 0, updates = 0
struct Wrapper: UIViewRepresentable {
var value: Int
func makeUIView(context: Context) -> UILabel { makes += 1; return UILabel() }
func updateUIView(_ v: UILabel, context: Context) { updates += 1; v.text = "\(value)" }
}
struct Root: View {
@State var n = 0
var body: some View { Wrapper(value: n) }
}
let host = UIHostingController(rootView: Root())
let window = UIWindow(frame: CGRect(x: 0, y: 0, width: 300, height: 300))
window.rootViewController = host
window.makeKeyAndVisible()
host.view.layoutIfNeeded()
print("first", makes, updates)
let d = (host.rootView)
_ = d
print("done")
- first 1 1
- first 1 0
- first 2 2
Mental model. The representable is a stage plan that SwiftUI reprints often; the UIKit view is the actor who stays on stage, and the coordinator is their agent who passes messages back.
To use a UIKit view in SwiftUI, conform to UIViewRepresentable (or UIViewControllerRepresentable). makeUIView(context:) creates the view once; updateUIView(_:context:) is called to push SwiftUI state into it, every time the struct’s inputs change and once after creation; the optional dismantleUIView cleans up. The representable struct itself is a cheap value that SwiftUI recreates freely, while the UIKit view it manages lives on, so keep state in the view or the coordinator, never in the struct’s own properties that you mutate.
For events flowing back (delegate callbacks, targets, text changes) use a coordinator: a class returned from makeCoordinator(), which becomes the UIKit object’s delegate and holds a binding or closure to write back into SwiftUI. In updateUIView guard against feedback loops (do not set a property that triggers the delegate which sets the binding which calls update again) by comparing the new value to the current one. Size follows SwiftUI layout, so give the view an intrinsic size or sizeThatFits. This is the bridge for map views, web views, camera previews and any component with no SwiftUI equivalent.
The view is created once and updated once to apply the initial value.
Common traps
- Setting state in
updateUIViewunconditionally. It can trigger the delegate, change the binding and loop. - Storing mutable state in the representable struct. SwiftUI recreates the struct, so it is lost.
They might follow up with
- Where does delegate code go? In the coordinator, which owns the delegate role and a binding to SwiftUI.
- Why does
updateUIViewrun immediately aftermakeUIView? To apply the initial state to the new view.
Related questions
- 7.9.1 How do you put SwiftUI inside a UIKit app, and what do you need to watch for?
- 7.1.3 How do gesture recognisers interact with touches and with each other?
- 4.4.1 What do @objc, @objcMembers, @objc(name), dynamic and @nonobjc each do?
← How do you put SwiftUI inside a UIKit app, and what do you need to…