Post

Replies

Boosts

Views

Activity

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 UITextFieldWorkaround 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.
1
0
27
52m
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 UITextFieldWorkaround 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.
Replies
1
Boosts
0
Views
27
Activity
52m