UIViewRepresentable feedback loops

SwiftUI Senior Swift 5.1+ iOS 17+ Reviewed By Alok Singh

Spot the bug

The map stutters and keeps snapping back while the user drags. Tap the line that causes it.

  1. struct MapView: UIViewRepresentable {
  2. @Binding var center: CLLocationCoordinate2D
  3. func makeUIView(context: Context) -> MKMapView {
  4. let map = MKMapView()
  5. map.delegate = context.coordinator
  6. return map
  7. }
  8. func updateUIView(_ map: MKMapView, context: Context) {
  9. map.setCenter(center, animated: true)
  10. }
  11. func makeCoordinator() -> Coordinator { Coordinator(self) }
  12. final class Coordinator: NSObject, MKMapViewDelegate {
  13. var parent: MapView
  14. init(_ parent: MapView) { self.parent = parent }
  15. func mapViewDidChangeVisibleRegion(_ map: MKMapView) {
  16. parent.center = map.centerCoordinate
  17. }
  18. }
  19. }
Hint 1 of 2

What does every SwiftUI update do to the map?

Hint 2 of 2

What happens after the delegate writes the binding?

Choose an answer, then open Show answer below to check it.

Show answer
  • Answer: line 9 map.setCenter(center, animated: true)

Line 9 re-centres the map on every update. Dragging writes the binding (line 16), which triggers an update, which re-centres the map, and so on. Only call setCenter when the new centre is meaningfully different from the map's current one.

30-second answer

Wrap the UIKit view in a UIViewRepresentable: create it once in makeUIView, push SwiftUI state into it in updateUIView, and receive UIKit events through a Coordinator. updateUIView runs often, so it must be cheap and must only apply real changes; otherwise a delegate callback that writes back to a binding can cause an update loop.

Practise saying it · 45 s. No recording, just a timer

Explain how to wrap a UIKit view in SwiftUI safely.

Say your answer out loud, then check which of these points you covered:

  • make once, update cheaply
  • Coordinator for delegates
  • Compare before set
  • dismantle for cleanup

Deep dive

Compare before setting: only call setters when the value really differs.

The Coordinator is the delegate, and it holds the latest parent value.

UIViewControllerRepresentable does the same for view controllers.

sizeThatFits(_:uiView:context:) (iOS 16) lets the wrapped view report its size.

Common mistakes

  • Creating the UIKit view in updateUIView.
  • Unconditionally setting values that fire delegate callbacks.
  • Holding the representable struct in the coordinator and expecting it to stay current without updating it.

Senior take & follow-ups

Seniors call out the update loop and the 'compare before set' rule.

Follow-up questions

Think of your answer first, then open the hint.

  1. Where do you clean up?

    Show hint

    static func dismantleUIView.

  2. How do you call UIKit methods from SwiftUI?

    Show hint

    Pass a small controller object, or use the coordinator.

From the author

Catalog - UI

Made by the author: browse UIKit and SwiftUI components with live demos, and copy their code snippets into your project.

Suggest a fix if something here is wrong or out of date.