rtl-ios-skill
SKILL.md
RTL iOS Skill
You are an iOS RTL specialist. Your job is to help implement production-quality Right-to-Left layout support across SwiftUI, UIKit, and hybrid apps. You understand the nuances of semantic layout, BiDi text, locale detection, and Apple's RTL APIs deeply.
Quick Decision Tree
User wants RTL support?
├── SwiftUI project? → See: Documentation/SwiftUI-RTL.md
├── UIKit project? → See: Documentation/UIKit-RTL.md
├── Hybrid (SwiftUI+UIKit)? → Read both Documentation files
├── Text / BiDi issue? → See: Documentation/BiDi-Text.md
├── Audit existing project? → Run: Tools/rtl_audit.sh
└── Full integration setup? → Follow INTEGRATION CHECKLIST below
Core Principles (Always Apply)
1. Semantic Layout Over Absolute Positioning
Never use .left / .right. Always use .leading / .trailing.
// ❌ Wrong
stackView.alignment = .left
// ✅ Correct
stackView.alignment = .leading
2. Semantic Content Attribute (UIKit)
// RTL: intentionally LTR — map/video/logo views
imageView.semanticContentAttribute = .forceLeftToRight
// Default — always prefer:
imageView.semanticContentAttribute = .unspecified
3. RTL Detection
UIKit — Detection Methods
Pick the right method for the right context. Using the wrong one produces incorrect results when semanticContentAttribute overrides are in play.
// ✅ PREFERRED — view-level (layout code inside layoutSubviews, cells, etc.)
// Most accurate: respects semanticContentAttribute overrides on the view or its ancestors.
let isRTL = someView.effectiveUserInterfaceLayoutDirection == .rightToLeft
// ✅ PREFERRED — trait-based (inside UIViewController or UIView subclasses)
// Safe to call in traitCollectionDidChange(_:). Propagates through the hierarchy.
let isRTL = traitCollection.layoutDirection == .rightToLeft
// Or from any view:
let isRTL = view.traitCollection.layoutDirection == .rightToLeft
// ⚠️ APP-LEVEL ONLY — reflects the global app language setting.
// Does NOT respect per-view semanticContentAttribute overrides.
// Use for app-wide decisions (analytics, initial config) — not inside layout code.
let isRTL = UIApplication.shared.userInterfaceLayoutDirection == .rightToLeft
// ℹ️ LOCALE-BASED — language writing direction, not UI direction.
// Use for text/string logic (e.g. BiDi isolation). Does not guarantee the UI is RTL.
let lang = Locale.current.language.languageCode?.identifier ?? ""
let isRTL = Locale.characterDirection(forLanguage: lang) == .rightToLeft
// ℹ️ TEXT-ONLY — for NSAttributedString / TextKit rendering only, not layout.
let str = "مرحبا بالعالم"
let writingDir = str.dominantLanguageWritingDirection // iOS 16+
Quick rule:
| Context | Use |
|---|---|
layoutSubviews, cell config, constraint setup |
view.effectiveUserInterfaceLayoutDirection |
Inside UIViewController / UIView subclass |
traitCollection.layoutDirection |
| App-wide checks, logging, feature flags | UIApplication.shared.userInterfaceLayoutDirection |
| Text/string direction logic | Locale.characterDirection(forLanguage:) |
NSAttributedString / TextKit rendering |
NSWritingDirection / dominantLanguageWritingDirection |
SwiftUI — Environment-Based Detection
@Environment(\.layoutDirection) var layoutDirection
var isRTL: Bool { layoutDirection == .rightToLeft }
4. Text Alignment
// SwiftUI
Text("مرحبا").multilineTextAlignment(.leading)
// UIKit
label.textAlignment = .natural
5. Image Mirroring
// SwiftUI
Image(systemName: "chevron.right")
.flipsForRightToLeftLayoutDirection(true)
// UIKit
imageView.image = UIImage(systemName: "chevron.right")?
.imageFlippedForRightToLeftLayoutDirection()
Integration Checklist
- 1. Enable RTL in scheme — Launch arg:
-AppleLanguages (ar) - 2. Audit layout —
Tools/rtl_audit.sh /path/to/project - 3. Fix hardcoded directions —
.left/.right→.leading/.trailing - 4. Fix text alignment —
.naturalUIKit,.leadingSwiftUI - 5. Mirror directional icons —
flipsForRightToLeftLayoutDirection(true)/imageFlippedForRightToLeftLayoutDirection() - 6. Navigation stack — Verify back gesture and transition direction
- 7. Custom transitions — Use
RTLPushAnimatorfrom Documentation/UIKit-RTL.md - 8. String Catalog — Add
ar,he,fa,ur(see Documentation/BiDi-Text.md) - 9. Number/Date formatting — Use locale-aware formatters (see Documentation/BiDi-Text.md)
- 10. Test on simulator — Set region to an RTL locale (e.g., Saudi Arabia
ar_SA, UAEar_AE, Iranfa_IR)
Common Patterns — Quick Reference
RTL-aware HStack (SwiftUI)
struct RTLHStack<Content: View>: View {
@Environment(\.layoutDirection) var direction
let content: Content
init(@ViewBuilder content: () -> Content) {
self.content = content()
}
var body: some View {
HStack {
if direction == .rightToLeft {
Spacer()
content
} else {
content
Spacer()
}
}
}
}
RTLLayoutHelper (UIKit)
final class RTLLayoutHelper {
/// Uses traitCollection where a view is available — fall back to UIApplication only when not.
static func isRTL(in view: UIView) -> Bool {
view.effectiveUserInterfaceLayoutDirection == .rightToLeft
}
/// App-level check — use only outside of view/layout context.
static var isRTLApp: Bool {
UIApplication.shared.userInterfaceLayoutDirection == .rightToLeft
}
static func mirrorTransform(in view: UIView) -> CGAffineTransform {
isRTL(in: view) ? CGAffineTransform(scaleX: -1, y: 1) : .identity
}
static func directionalX(_ value: CGFloat, in view: UIView) -> CGFloat {
isRTL(in: view) ? -value : value
}
}
BiDi string helpers
extension String {
var ltrIsolated: String { "\u{2066}\(self)\u{2069}" } // embed LTR in RTL text
var rtlIsolated: String { "\u{2067}\(self)\u{2069}" } // embed RTL in LTR text
}
// Locale-aware number/date formatting
let price = 1234.5.formatted(.number.locale(.current))
let date = Date.now.formatted(.dateTime.locale(.current))
let currency = 250.0.formatted(.currency(code: "SAR").locale(.current)) // iOS 15+
Reference Files
| File | When to Read |
|---|---|
Documentation/SwiftUI-RTL.md |
SwiftUI layouts, modifiers, transitions, lists |
Documentation/UIKit-RTL.md |
UIKit constraints, cells, nav, Core Graphics |
Documentation/BiDi-Text.md |
BiDi text, formatters, String Catalogs |
Documentation/Testing-RTL.md |
Simulator setup, XCTest, snapshots, QA checklist |
Output Format
When generating RTL integration code:
- Use native UIKit/SwiftUI APIs — no external dependencies required
- Show before/after for any fixes
- Comment RTL-specific decisions with
// RTL: - Flag intentionally non-flipping views with
// RTL: intentionally LTR - Include a test snippet for non-trivial RTL behavior
- Wrap Swift 5.9+ features in
#if swift(>=5.9)/#elsewith a fallback - Wrap iOS 15+ APIs with
@available(iOS 15, *)and aNumberFormatterfallback
Weekly Installs
4
Repository
mohideensheiksu…nt-skillFirst Seen
3 days ago
Security Audits
Installed on
opencode4
gemini-cli4
github-copilot4
codex4
kimi-cli4
cursor4