iOS 27: SwiftUI TextField text invisible while typing when the app runs in Arabic (FB25410049)

Since iOS 27, if an app runs in Arabic (or, presumably, any right-to-left language), typing digits or Latin letters into a SwiftUI TextField makes the field look empty. The binding updates and the caret shows, but the text isn't drawn until editing ends.

What I measured:

  • The text's layout fragment is placed at about x = −39,655 pt in a 370 pt wide field, so it's drawn far outside the field.
  • A plain TextField and one with .environment(\.layoutDirection, .leftToRight) (phone and card numbers) are both affected.
  • A UIKit UITextField on the same screen isn't affected.
  • The same build on iOS 18.0 works, and iOS 27 in English works.

Minimal repro: one SwiftUI file with four fields. Run it with App Language set to Arabic, tap the first field and type 523. Launch with -autoProbe to have it type into each field and print where the text was laid out.

PROBE iOS 27.0, language ar, layout RTL
PROBE field 1 text="523" fieldWidth=370 fragmentX=[-39655] visible=false   // SwiftUI TextField
PROBE field 2 text="523" fieldWidth=370 fragmentX=[-39655] visible=false   // SwiftUI, LTR pinned
PROBE field 3 text="523" fieldWidth=370 fragmentX=[314]    visible=true    // UIKit UITextField

Workaround that works for us, installed once at launch:

import UIKit

enum TextFieldDirection {
    static func install() {
        for name in [UITextField.textDidBeginEditingNotification, UITextField.textDidChangeNotification] {
            NotificationCenter.default.addObserver(forName: name, object: nil, queue: .main) { note in
                guard let field = note.object as? UITextField else { return }
                MainActor.assumeIsolated { settle(field) }
                // SwiftUI rebuilds the paragraph style right after; settle again.
                DispatchQueue.main.async { MainActor.assumeIsolated { settle(field) } }
            }
        }
    }

    @MainActor static func settle(_ field: UITextField) {
        let direction: NSWritingDirection =
            field.effectiveUserInterfaceLayoutDirection == .rightToLeft ? .rightToLeft : .leftToRight
        if let whole = field.textRange(from: field.beginningOfDocument, to: field.endOfDocument) {
            field.setBaseWritingDirection(direction, for: whole)
        }
        func directed(_ attributes: [NSAttributedString.Key: Any]) -> [NSAttributedString.Key: Any] {
            var attributes = attributes
            let style = (attributes[.paragraphStyle] as? NSParagraphStyle)?.mutableCopy() as? NSMutableParagraphStyle
                ?? NSMutableParagraphStyle()
            style.baseWritingDirection = direction
            attributes[.paragraphStyle] = style
            return attributes
        }
        if let typing = field.typingAttributes { field.typingAttributes = directed(typing) }
        field.defaultTextAttributes = directed(field.defaultTextAttributes)
        field.setNeedsLayout()
        field.layoutIfNeeded()
    }
}

Don't pin textAlignment instead. SwiftUI puts .natural back on every keystroke and the caret jumps from side to side.

If you see this too, please file a duplicate referencing FB25410049. Duplicates help Apple prioritise it.

Thank you for your submission and for identifying this issue, including the associated project. The report has been routed to the appropriate team for review; they will contact you via the Feedback Assistant through which you filed the report.

You can see the status of your feedback in
Feedback Assistant.
There, you can track if the report is still being investigated,
has a potential identified fix, or has been resolved in another way.
The status appears beside the label "Resolution."
We're unable to share any updates on specific reports on the forums.

For more details on when you'll see updates to your report, please see
What to expect after submission.

Albert  WWDR