Quick Tip

Tracking value sources to prevent recursive SwiftUI updates

When wrapping a UITextView or NSTextView for use in SwiftUI, delegate updates are usually written back to a SwiftUI binding. SwiftUI then calls updateUIView(_:context:) or updateNSView(_:context:) with the same value on the next run loop iteration. Applying unchanged text can perform unnecessary layout work and move the insertion point while the user is typing, so a typical UIViewRepresentable compares the strings before updating the text view.

// UITextViewDelegate callback
func textViewDidChange(_ textView: UITextView) {
    text = textView.text
}

// UIViewRepresentable property
@Binding var text: String

func updateUIView(_ textView: UITextView, context: Context) {
    // Comparing large strings on every update can be expensive.
    guard textView.text != text else { return }
    textView.text = text
}

For large text values, we can avoid the string comparison by using a custom SwiftUI transaction value to record where the update originated. First, define an optional ObjectIdentifier on Transaction using the @Entry macro.

extension Transaction {
    @Entry var originatingTextView: ObjectIdentifier? = nil
}

In textViewDidChange(_:), wrap the binding write in withTransaction(::_:) and store the identity of the text view that produced the update.

func textViewDidChange(_ textView: UITextView) {
    withTransaction(
        \.originatingTextView,
        ObjectIdentifier(textView)
    ) {
        text = textView.text
    }
}

The representable can then compare the transaction value with the identity of the text view being updated.

func updateUIView(_ textView: UITextView, context: Context) {
    // Comparing fixed-size identity values is inexpensive.
    guard context.transaction.originatingTextView != ObjectIdentifier(textView) else {
        return
    }
    textView.text = text
}

Updates made elsewhere that omit the custom withTransaction call use the default nil value and continue to update the wrapped view normally.

The SwiftUI Way by Natalia Panferova book coverThe SwiftUI Way by Natalia Panferova book cover

Work with SwiftUI. Not against it.$35

A field guide to SwiftUI patterns and anti-patterns

The SwiftUI Wayby Natalia Panferova

  • Avoid common SwiftUI pitfalls
  • Build deeper intuition for the framework
  • Gain insights from a former SwiftUI Engineer at Apple

Work with SwiftUI. Not against it.

A field guide to SwiftUI patterns and anti-patterns

The SwiftUI Way by Natalia Panferova book coverThe SwiftUI Way by Natalia Panferova book cover

The SwiftUI Way

by Natalia Panferova

$35