Irbisa · Cheatsheet
Accessibility & Localization
VoiceOver, traits, Dynamic Type, Reduce Motion, String Catalogs, plurals, RTL, locale formatting
What does making an iOS app accessible involve: which users do you build for, and which system features do you support?
What are an element's accessibility label, value, traits and hint, and in what order does VoiceOver read them?
// wrong: type and state are baked into the labelButton(action: toggle) { Image(systemName: icon) } .accessibilityLabel(isOn ? "Favourite button, on" : "Favourite button, off") // right: each piece in its own propertyButton(action: toggle) { Image(systemName: icon) }…What does VoiceOver say as it swipes through this row, and what would you change?
struct UserRow: View { let user: User let onDelete: () -> Void var body: some View { HStack { Image("avatar_placeholder") Text(user.name) Spacer() Button(action: onDelete) { Image("ic_trash") } } }}What is Dynamic Type, and how do you support it in SwiftUI and UIKit?
// SwiftUIText("Order history").font(.headline) // scalesText(order.note) .font(.custom("Brand", size: 15, relativeTo: .subheadline))Text("v2.4.1").font(.system(size: 11)) // fixed: never scales…How do you localize an iOS app, and what must never be hardcoded?
// SwiftUI: literals are localization keysText("Order history")Button("Delete", role: .destructive) { delete() } // Outside a view: resolve now, and give the translator contextlet title = String(localized: "Delete order?",…A product card is five VoiceOver stops: image, title, price, badge, heart button. How do you make it one, and when should you not?
HStack { AsyncImage(url: product.imageURL) .accessibilityHidden(true) // decoration: hide before combining VStack(alignment: .leading) { Text(product.name) Text(product.price.formatted(.currency(code: product.currency)))…VoiceOver reads a card's price before its title, although the title is drawn above it. How do you control the reading order?
// SwiftUI: price is declared first but drawn below the title; the title must be read firstZStack(alignment: .topLeading) { Text(album.price) .offset(y: 24) .accessibilitySortPriority(1) Text(album.title)…A row has swipe-to-delete and a custom volume dial. How does a VoiceOver user reach them without those gestures?
MessageRow(message: message) .accessibilityAction(named: "Delete") { delete(message) } .accessibilityAction(named: "Mark as read") { markRead(message) } VolumeDial(value: $volume) .accessibilityLabel("Volume")…After a save VoiceOver jumps to the top of the screen and re-reads it. Which notification was posted by mistake and which should be used?
// wrong: resets the user's position and re-reads the screenUIAccessibility.post(notification: .screenChanged, argument: "Saved") // right: speak, and leave focus aloneUIAccessibility.post(notification: .announcement, argument: String(localized: "Draft saved"))…A form fails validation. How do you move VoiceOver focus to the error message in SwiftUI?
struct SignUpForm: View { @State private var email = "" @State private var error: String? @AccessibilityFocusState private var errorFocused: Bool var body: some View {…QA reports that this row ignores the user's text size, and that once it scales it clips at the largest sizes. What do you change?
HStack(spacing: 12) { Image(systemName: "envelope") .frame(width: 40, height: 40) Text(item.title) .font(.system(size: 17)) .lineLimit(1)}.frame(height: 44)Find the bug: the user raises the text size from Control Center, and this header's labels grow but stay side by side and clip until the screen is reopened.
final class ContactHeaderViewController: UIViewController { private let stack = UIStackView(arrangedSubviews: [UILabel(), UILabel()]) override func viewDidLoad() { super.viewDidLoad() for case let label as UILabel in stack.arrangedSubviews { label.font = .preferredFont(forTextStyle: .body) label.adjustsFontForContentSizeCategory = true } view.addSubview(stack) stack.frame = view.bounds } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) stack.axis = traitCollection.preferredContentSizeCategory.isAccessibilityCategory ? .vertical : .horizontal }}What are Reduce Motion, Reduce Transparency, Increase Contrast and Bold Text, and how do you honour them in your own UI?
struct Hero: View { let isOpen: Bool let parallaxOffset: CGFloat @Environment(\.accessibilityReduceMotion) private var reduceMotion @Environment(\.accessibilityReduceTransparency) private var reduceTransparency @Environment(\.colorSchemeContrast) private var contrast…Design marks paid orders with a green dot and overdue ones with a red dot. What is wrong with that, and how do you fix it?
struct OrderRow: View { let order: Order var body: some View { HStack { Image(systemName: order.isPaid ? "checkmark.circle.fill"…How do you test an app's accessibility: what do the Accessibility Inspector and
performAccessibilityAudit()catch, and what do they miss?final class AccessibilityAuditTests: XCTestCase { func testHomeScreenPassesAudit() throws { let app = XCUIApplication() app.launch() try app.performAccessibilityAudit(for: [.contrast, .hitRegion, .textClipped]) { issue in…What is a String Catalog, and what do the New, Needs Review and Stale states mean after a build?
The string is
"\(count) items"and the Russian build shows «1 товаров». How should plurals be handled?// wrong: an English-only rule baked into the call sitelet label = count == 1 ? "1 item" : "\(count) items" // right: one key, every language brings its own formsText("\(count) items") // catalog key: "%lld items"…In the German build the navigation title is translated but the section titles stay English: why, and which type fixes it?
struct LibrarySection: Identifiable { let id = UUID() let title: String} let sections = [LibrarySection(title: "Recent"), LibrarySection(title: "Favourites")] List(sections) { section in Text(section.title)}.navigationTitle("Library")How do you format dates, numbers and prices for the user, and who decides the format when the language is English but the region is Germany?
// wrong: the developer chose the pattern and the symbollet df = DateFormatter()df.dateFormat = "MM/dd/yyyy hh:mm a"Text("$\(order.total)") // right: the locale decides presentation, the data decides the currency…Find the bug: in the French and German builds of the country picker, several countries sit at the very bottom of the list.
struct CountryPicker: View { let regionCodes: [String] // "AE", "AT", "DE", "ZW", … @Binding var selection: String private var countries: [(code: String, name: String)] { regionCodes .map { ($0, Locale.current.localizedString(forRegionCode: $0) ?? $0) } .sorted { $0.name < $1.name } } var body: some View { Picker("Country", selection: $selection) { ForEach(countries, id: \.code) { Text(verbatim: $0.name).tag($0.code) } } }}Product wants a language switcher inside the app: what does iOS already give you, and why is writing
AppleLanguagestoUserDefaultsa hack?How do you support right-to-left languages like Arabic: what mirrors automatically, what do you flip yourself, and what must never flip?
// A directional custom icon: mirror it in RTLImage("arrow_next") .flipsForRightToLeftLayoutDirection(true) // Media controls keep their direction in a mirrored screenPlayerControls()…A bar chart is drawn in a single
Canvasor adraw(_:)override. How do you make each bar reachable with VoiceOver?struct SalesChart: View { let sales: [MonthlySales] // Identifiable: month, amount var body: some View { Canvas { context, size in drawBars(sales, in: context, size: size) } .accessibilityLabel("Sales by month") .accessibilityChildren { // never drawn, only exposed to VoiceOver…Translations arrive two weeks before release. How do you catch truncation and hardcoded strings in CI long before that?
final class LocalizationLayoutTests: XCTestCase { func testCheckoutFitsLongStringsAndLargeText() throws { let app = XCUIApplication() // Language and region come from the test plan configuration app.launchArguments += [ "-UIPreferredContentSizeCategoryName", "UICTContentSizeCategoryAccessibilityXXXL",…Beyond the app UI, where do you localize the App Store listing, push notifications and App Shortcuts?