āļ„āļģāļ–āļēāļĄāļŠāļąāļĄāļ āļēāļĐāļ“āđŒāļāļēāļĢāđ€āļ‚āđ‰āļēāļ–āļķāļ‡ iOS āđƒāļ™āļ›āļĩ 2026: VoiceOver āđāļĨāļ° Dynamic Type

āđ€āļ•āļĢāļĩāļĒāļĄāļ•āļąāļ§āļŠāļąāļĄāļ āļēāļĐāļ“āđŒ iOS āļ”āđ‰āļ§āļĒāļ„āļģāļ–āļēāļĄāļŠāļģāļ„āļąāļāđ€āļĢāļ·āđˆāļ­āļ‡āļāļēāļĢāđ€āļ‚āđ‰āļēāļ–āļķāļ‡: VoiceOver, Dynamic Type, traits āđ€āļŠāļīāļ‡āļ„āļ§āļēāļĄāļŦāļĄāļēāļĒ āđāļĨāļ°āļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļš.

āļ„āļģāļ–āļēāļĄāļŠāļąāļĄāļ āļēāļĐāļ“āđŒāļāļēāļĢāđ€āļ‚āđ‰āļēāļ–āļķāļ‡ iOS: VoiceOver āđāļĨāļ° Dynamic Type

āļ„āļ§āļēāļĄāļŠāļēāļĄāļēāļĢāļ–āđƒāļ™āļāļēāļĢāđ€āļ‚āđ‰āļēāļ–āļķāļ‡āļāļĨāļēāļĒāđ€āļ›āđ‡āļ™āļ—āļąāļāļĐāļ°āļŠāļģāļ„āļąāļāļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļžāļąāļ’āļ™āļē iOS āļœāļđāđ‰āļŠāļĢāļĢāļŦāļēāļšāļļāļ„āļĨāļēāļāļĢāļ›āļĢāļ°āđ€āļĄāļīāļ™āļ„āļ§āļēāļĄāđ€āļŠāļĩāđˆāļĒāļ§āļŠāļēāļāđƒāļ™ VoiceOver, Dynamic Type āđāļĨāļ° API āļāļēāļĢāđ€āļ‚āđ‰āļēāļ–āļķāļ‡āļ­āļĒāđˆāļēāļ‡āđ€āļ›āđ‡āļ™āļĢāļ°āļšāļš āļ„āļģāļ–āļēāļĄāļ•āđˆāļ­āđ„āļ›āļ™āļĩāđ‰āļ„āļĢāļ­āļšāļ„āļĨāļļāļĄāđāļ™āļ§āļ„āļīāļ”āļŠāļģāļ„āļąāļāļ—āļĩāđˆāļˆāļ°āļŠāđˆāļ§āļĒāđƒāļŦāđ‰āļŠāļąāļĄāļ āļēāļĐāļ“āđŒāđ€āļ—āļ„āļ™āļīāļ„āļŠāļģāđ€āļĢāđ‡āļˆ.

āđ€āļŦāļ•āļļāđƒāļ”āļāļēāļĢāđ€āļ‚āđ‰āļēāļ–āļķāļ‡āļˆāļķāļ‡āļŠāļģāļ„āļąāļāđƒāļ™āļāļēāļĢāļŠāļąāļĄāļ āļēāļĐāļ“āđŒ

āļĄāļĩāļœāļđāđ‰āļ„āļ™āļāļ§āđˆāļē 1 āļžāļąāļ™āļĨāđ‰āļēāļ™āļ„āļ™āļ—āļĩāđˆāđƒāļŠāđ‰āļŠāļĩāļ§āļīāļ•āļāļąāļšāļ„āļ§āļēāļĄāļžāļīāļāļēāļĢ Apple āļšāļąāļ‡āļ„āļąāļšāđƒāļŠāđ‰āļĄāļēāļ•āļĢāļāļēāļ™āļāļēāļĢāđ€āļ‚āđ‰āļēāļ–āļķāļ‡āļ—āļĩāđˆāđ€āļ‚āđ‰āļĄāļ‡āļ§āļ” āđāļĨāļ°āļŦāļĨāļēāļĒāļšāļĢāļīāļĐāļąāļ—āļ›āļāļīāđ€āļŠāļ˜āļ—āļĩāđˆāļˆāļ°āđ€āļœāļĒāđāļžāļĢāđˆāđāļ­āļ›āļ—āļĩāđˆāđ„āļĄāđˆāļŠāļēāļĄāļēāļĢāļ–āđ€āļ‚āđ‰āļēāļ–āļķāļ‡āđ„āļ”āđ‰ āļ—āļąāļāļĐāļ°āļ™āļĩāđ‰āđāļĒāļāļœāļđāđ‰āļŠāļĄāļąāļ„āļĢāļĢāļ°āļ”āļąāļšāļ‹āļĩāđ€āļ™āļĩāļĒāļĢāđŒāļ­āļ­āļāļˆāļēāļāļ„āļ™āļ­āļ·āđˆāļ™.

āļžāļ·āđ‰āļ™āļāļēāļ™āļāļēāļĢāđ€āļ‚āđ‰āļēāļ–āļķāļ‡āļšāļ™ iOS

1. āđ€āļ„āļĢāļ·āđˆāļ­āļ‡āļĄāļ·āļ­āļāļēāļĢāđ€āļ‚āđ‰āļēāļ–āļķāļ‡āļŦāļĨāļąāļāļ‚āļ­āļ‡ iOS āļĄāļĩāļ­āļ°āđ„āļĢāļšāđ‰āļēāļ‡?

iOS āļĄāļĩāļĢāļ°āļšāļšāļ™āļīāđ€āļ§āļĻāļ‚āļ­āļ‡āđ€āļ„āļĢāļ·āđˆāļ­āļ‡āļĄāļ·āļ­āļāļēāļĢāđ€āļ‚āđ‰āļēāļ–āļķāļ‡āļ—āļĩāđˆāļ„āļĢāļ­āļšāļ„āļĨāļļāļĄāļŦāļĨāļēāļĒāļ›āļĢāļ°āđ€āļ āļ—āļ„āļ§āļēāļĄāļžāļīāļāļēāļĢ VoiceOver āļŠāđˆāļ§āļĒāļ­āđˆāļēāļ™āļŦāļ™āđ‰āļēāļˆāļ­āļŠāļģāļŦāļĢāļąāļšāļœāļđāđ‰āļžāļīāļāļēāļĢāļ—āļēāļ‡āļŠāļēāļĒāļ•āļē Dynamic Type āļ›āļĢāļąāļšāļ‚āļ™āļēāļ”āļ‚āđ‰āļ­āļ„āļ§āļēāļĄ Switch Control āđ€āļ›āļīāļ”āđƒāļŦāđ‰āļ„āļ§āļšāļ„āļļāļĄāļ”āđ‰āļ§āļĒāļŠāļ§āļīāļ•āļŠāđŒāļ āļēāļĒāļ™āļ­āļ Voice Control āđƒāļŦāđ‰āļ„āļģāļŠāļąāđˆāļ‡āđ€āļŠāļĩāļĒāļ‡āđ€āļ•āđ‡āļĄāļĢāļđāļ›āđāļšāļš.

AccessibilityTools.swiftswift
// Check if VoiceOver is running
if UIAccessibility.isVoiceOverRunning {
    // Adapt the interface for VoiceOver
    showSimplifiedInterface()
}

// Check if Reduce Motion is enabled
if UIAccessibility.isReduceMotionEnabled {
    // Disable complex animations
    animationDuration = 0
}

// Check if Bold Text is enabled
if UIAccessibility.isBoldTextEnabled {
    // Use heavier fonts
    applyBoldFonts()
}

// Observe settings changes
NotificationCenter.default.addObserver(
    forName: UIAccessibility.voiceOverStatusDidChangeNotification,
    object: nil,
    queue: .main
) { _ in
    // React to the change
    updateAccessibilityLayout()
}

API āđ€āļŦāļĨāđˆāļēāļ™āļĩāđ‰āļ—āļģāđƒāļŦāđ‰āļŠāļēāļĄāļēāļĢāļ–āļ›āļĢāļąāļšāļ­āļīāļ™āđ€āļ—āļ­āļĢāđŒāđ€āļŸāļ‹āđāļšāļšāđ„āļ”āļ™āļēāļĄāļīāļāļ•āļēāļĄāļ„āđˆāļēāļ•āļąāđ‰āļ‡āļ‚āļ­āļ‡āļœāļđāđ‰āđƒāļŠāđ‰āđāļĨāļ°āđ€āļ—āļ„āđ‚āļ™āđ‚āļĨāļĒāļĩāļŠāđˆāļ§āļĒāđ€āļŦāļĨāļ·āļ­āļ—āļĩāđˆāļ—āļģāļ‡āļēāļ™āļ­āļĒāļđāđˆ.

2. VoiceOver āļ—āļģāļ‡āļēāļ™āļ—āļēāļ‡āđ€āļ—āļ„āļ™āļīāļ„āļ­āļĒāđˆāļēāļ‡āđ„āļĢ?

VoiceOver āđ„āļĨāđˆāļœāđˆāļēāļ™āļ­āļīāļ™āđ€āļ—āļ­āļĢāđŒāđ€āļŸāļ‹āđ€āļžāļ·āđˆāļ­āļĢāļ°āļšāļļāļ­āļ‡āļ„āđŒāļ›āļĢāļ°āļāļ­āļšāļ—āļĩāđˆāđ€āļ‚āđ‰āļēāļ–āļķāļ‡āđ„āļ”āđ‰ āđāļ•āđˆāļĨāļ°āļ­āļ‡āļ„āđŒāļ›āļĢāļ°āļāļ­āļšāđ€āļ›āļīāļ”āđ€āļœāļĒāļ„āļļāļ“āļŠāļĄāļšāļąāļ•āļīāđ€āļŠāđˆāļ™ label, value, traits āđāļĨāļ° actions āđ‚āļ›āļĢāđāļāļĢāļĄāļ­āđˆāļēāļ™āļŦāļ™āđ‰āļēāļˆāļ­āļˆāļ°āļŠāļąāļ‡āđ€āļ„āļĢāļēāļ°āļŦāđŒāđ€āļŠāļĩāļĒāļ‡āļ‚āđ‰āļ­āļĄāļđāļĨāđ€āļŦāļĨāđˆāļēāļ™āļĩāđ‰āđ€āļĄāļ·āđˆāļ­āļœāļđāđ‰āđƒāļŠāđ‰āļ™āļģāļ—āļēāļ‡āļ”āđ‰āļ§āļĒāļ—āđˆāļēāļ—āļēāļ‡.

VoiceOverBasics.swiftswift
// In UIKit: configure an accessible element
class CustomButton: UIButton {
    override var accessibilityLabel: String? {
        get { "Validation button" }  // What VoiceOver reads
        set { }
    }

    override var accessibilityHint: String? {
        get { "Double-tap to confirm the order" }  // Possible action
        set { }
    }

    override var accessibilityTraits: UIAccessibilityTraits {
        get { .button }  // Element type
        set { }
    }

    override var isAccessibilityElement: Bool {
        get { true }  // Element focusable by VoiceOver
        set { }
    }
}

// In SwiftUI: equivalent with modifiers
struct ValidateButton: View {
    var body: some View {
        Button("Validate") {
            confirmOrder()
        }
        .accessibilityLabel("Validation button")
        .accessibilityHint("Double-tap to confirm the order")
    }
}

āļĨāļģāļ”āļąāļšāļŠāļąāđ‰āļ™āļ‚āļ­āļ‡āļāļēāļĢāđ€āļ‚āđ‰āļēāļ–āļķāļ‡āļ­āļēāļˆāđāļ•āļāļ•āđˆāļēāļ‡āļˆāļēāļāļĨāļģāļ”āļąāļšāļŠāļąāđ‰āļ™āļ—āļēāļ‡āļŠāļēāļĒāļ•āļē āļ—āļģāđƒāļŦāđ‰āļ™āļģāļ—āļēāļ‡āļ•āļēāļĄāļ•āļĢāļĢāļāļ°āđ„āļ”āđ‰āđāļĄāđ‰āđƒāļ™āđ€āļĨāļĒāđŒāđ€āļ­āļēāļ•āđŒāļ—āļĩāđˆāļ‹āļąāļšāļ‹āđ‰āļ­āļ™.

3. accessibilityLabel āđāļĨāļ° accessibilityValue āļ•āđˆāļēāļ‡āļāļąāļ™āļ­āļĒāđˆāļēāļ‡āđ„āļĢ?

accessibilityLabel āļĢāļ°āļšāļļāļ­āļ‡āļ„āđŒāļ›āļĢāļ°āļāļ­āļšāļ­āļĒāđˆāļēāļ‡āļ–āļēāļ§āļĢ āļ‚āļ“āļ°āļ—āļĩāđˆ accessibilityValue āđāļŠāļ”āļ‡āļŠāļ–āļēāļ™āļ°āļ›āļąāļˆāļˆāļļāļšāļąāļ™āļ—āļĩāđˆāļ­āļēāļˆāđ€āļ›āļĨāļĩāđˆāļĒāļ™āđāļ›āļĨāļ‡ āļāļēāļĢāđāļĒāļāđāļĒāļ°āļ™āļĩāđ‰āļŠāļģāļ„āļąāļāļĄāļēāļāļāļąāļšāļāļēāļĢāļ„āļ§āļšāļ„āļļāļĄāđ„āļ”āļ™āļēāļĄāļīāļāļ­āļĒāđˆāļēāļ‡ slider āļŦāļĢāļ·āļ­ switch.

LabelVsValue.swiftswift
// Example with a volume slider
class VolumeSlider: UISlider {
    override var accessibilityLabel: String? {
        get { "Volume" }  // Fixed element identity
        set { }
    }

    override var accessibilityValue: String? {
        get { "\(Int(value * 100)) percent" }  // Current state that changes
        set { }
    }
}
// VoiceOver reads: "Volume, 75 percent"

// In SwiftUI with a Toggle
struct NotificationToggle: View {
    @Binding var isEnabled: Bool

    var body: some View {
        Toggle(isOn: $isEnabled) {
            Text("Notifications")
        }
        .accessibilityLabel("Push notifications")
        // accessibilityValue is automatic for Toggle: "on" or "off"
    }
}

// For a custom stepper
struct QuantityStepper: View {
    @State private var quantity = 1

    var body: some View {
        Stepper("Quantity", value: $quantity, in: 1...99)
            .accessibilityLabel("Item quantity")
            .accessibilityValue("\(quantity) item\(quantity > 1 ? "s" : "")")
    }
}

VoiceOver āļˆāļ°āļ­āđˆāļēāļ™ label āļ•āļēāļĄāļ”āđ‰āļ§āļĒ value āđ€āļŠāļĄāļ­ āļ—āļģāđƒāļŦāđ‰āļœāļđāđ‰āđƒāļŠāđ‰āđ€āļ‚āđ‰āļēāđƒāļˆāļ—āļąāđ‰āļ‡āļ•āļąāļ§āļ­āļ‡āļ„āđŒāļ›āļĢāļ°āļāļ­āļšāđāļĨāļ°āļŠāļ–āļēāļ™āļ°āļ›āļąāļˆāļˆāļļāļšāļąāļ™.

4. āļ­āļ˜āļīāļšāļēāļĒ accessibilityTraits āđāļĨāļ°āļ„āļ§āļēāļĄāļŠāļģāļ„āļąāļ

Traits āļšāļ­āļ VoiceOver āļ–āļķāļ‡āļ›āļĢāļ°āđ€āļ āļ—āđāļĨāļ°āļžāļĪāļ•āļīāļāļĢāļĢāļĄāļ‚āļ­āļ‡āļ­āļ‡āļ„āđŒāļ›āļĢāļ°āļāļ­āļš āļĄāļąāļ™āļāļģāļŦāļ™āļ”āļ§āļīāļ˜āļĩāļ­āđˆāļēāļ™āļ­āļ‡āļ„āđŒāļ›āļĢāļ°āļāļ­āļšāđāļĨāļ°āļāļēāļĢāļāļĢāļ°āļ—āļģāļ—āļĩāđˆāđƒāļŠāđ‰āđ„āļ”āđ‰ āļ›āļļāđˆāļĄāļ—āļĩāđˆāđ„āļĄāđˆāļĄāļĩ trait .button āļˆāļ°āđ„āļĄāđˆāļ–āļđāļāļ›āļĢāļ°āļāļēāļĻāđ€āļ›āđ‡āļ™āļ›āļļāđˆāļĄ.

AccessibilityTraits.swiftswift
// Common traits in UIKit
class CustomCell: UITableViewCell {
    override var accessibilityTraits: UIAccessibilityTraits {
        get {
            var traits: UIAccessibilityTraits = []

            // Element type
            traits.insert(.button)           // Interactive element

            // Current state
            if isSelected {
                traits.insert(.selected)     // Currently selected
            }

            // Special characteristics
            if !isEnabled {
                traits.insert(.notEnabled)   // Disabled
            }

            return traits
        }
        set { }
    }
}

// Available traits
// .button         - Clickable element
// .link           - Hyperlink
// .header         - Section header
// .image          - Image
// .selected       - Selected state
// .notEnabled     - Disabled
// .adjustable     - Adjustable value (slider)
// .staticText     - Non-interactive text
// .searchField    - Search field
// .playsSound     - Plays a sound
// .startsMediaSession - Starts media playback

// In SwiftUI
struct SectionHeader: View {
    let title: String

    var body: some View {
        Text(title)
            .font(.headline)
            .accessibilityAddTraits(.isHeader)  // Announced as header
    }
}

struct SelectableRow: View {
    let isSelected: Bool

    var body: some View {
        HStack {
            Text("Option")
            if isSelected {
                Image(systemName: "checkmark")
            }
        }
        .accessibilityElement(children: .combine)
        .accessibilityAddTraits(isSelected ? .isSelected : [])
    }
}

Traits āļŠāđˆāļ§āļĒāđƒāļŦāđ‰āđ€āļ—āļ„āđ‚āļ™āđ‚āļĨāļĒāļĩāļŠāđˆāļ§āļĒāđ€āļŦāļĨāļ·āļ­āļĄāļ­āļšāļ›āļĢāļ°āļŠāļšāļāļēāļĢāļ“āđŒāļ—āļĩāđˆāļŠāļ­āļ”āļ„āļĨāđ‰āļ­āļ‡āđāļĨāļ°āļ„āļēāļ”āđ€āļ”āļēāđ„āļ”āđ‰āđƒāļŦāđ‰āļœāļđāđ‰āđƒāļŠāđ‰.

Dynamic Type āđāļĨāļ°āļ‚āļ™āļēāļ”āļ‚āđ‰āļ­āļ„āļ§āļēāļĄāļ—āļĩāđˆāļ›āļĢāļąāļšāļ•āļąāļ§āđ„āļ”āđ‰

5. āļ—āļģ Dynamic Type āđƒāļŦāđ‰āļ–āļđāļāļ•āđ‰āļ­āļ‡āļ­āļĒāđˆāļēāļ‡āđ„āļĢ?

Dynamic Type āđƒāļŦāđ‰āļœāļđāđ‰āđƒāļŠāđ‰āļ›āļĢāļąāļšāļ‚āļ™āļēāļ”āļ‚āđ‰āļ­āļ„āļ§āļēāļĄāļ‚āļ­āļ‡āļĢāļ°āļšāļš āļāļēāļĢāļžāļąāļ’āļ™āļēāļ—āļĩāđˆāļ–āļđāļāļ•āđ‰āļ­āļ‡āđƒāļŠāđ‰āļŠāđ„āļ•āļĨāđŒāļ‚āđ‰āļ­āļ„āļ§āļēāļĄāđ€āļŠāļīāļ‡āļ„āļ§āļēāļĄāļŦāļĄāļēāļĒāđāļĨāļ°āļ‚āđ‰āļ­āļˆāļģāļāļąāļ”āļ—āļĩāđˆāļĒāļ·āļ”āļŦāļĒāļļāđˆāļ™āđ€āļžāļ·āđˆāļ­āđƒāļŦāđ‰āļ­āļīāļ™āđ€āļ—āļ­āļĢāđŒāđ€āļŸāļ‹āļ›āļĢāļąāļšāļ•āļąāļ§āļ­āļąāļ•āđ‚āļ™āļĄāļąāļ•āļī.

DynamicType.swiftswift
// In UIKit: use preferred text styles
class ArticleCell: UITableViewCell {
    let titleLabel = UILabel()
    let bodyLabel = UILabel()

    func configure() {
        // Use semantic text styles
        titleLabel.font = UIFont.preferredFont(forTextStyle: .headline)
        bodyLabel.font = UIFont.preferredFont(forTextStyle: .body)

        // CRUCIAL: enable automatic adjustment
        titleLabel.adjustsFontForContentSizeCategory = true
        bodyLabel.adjustsFontForContentSizeCategory = true

        // Allow multiline for large sizes
        titleLabel.numberOfLines = 0
        bodyLabel.numberOfLines = 0
    }
}

// In SwiftUI: it's automatic with styles
struct ArticleView: View {
    var body: some View {
        VStack(alignment: .leading) {
            Text("Article Title")
                .font(.headline)  // Adapts automatically

            Text("Article content...")
                .font(.body)
        }
    }
}

// Custom font with Dynamic Type
extension UIFont {
    static func customFont(
        size: CGFloat,
        style: TextStyle
    ) -> UIFont {
        let customFont = UIFont(name: "CustomFont-Regular", size: size)!

        // Adapt to current size category
        return UIFontMetrics(forTextStyle: style)
            .scaledFont(for: customFont)
    }
}

āļŦāļēāļāđ„āļĄāđˆāđƒāļŠāđ‰ adjustsFontForContentSizeCategory label āļˆāļ°āļ„āļ‡āļ‚āļ™āļēāļ”āđ€āļĢāļīāđˆāļĄāļ•āđ‰āļ™āđāļĄāđ‰āļœāļđāđ‰āđƒāļŠāđ‰āļˆāļ°āđ€āļ›āļĨāļĩāđˆāļĒāļ™āļāļēāļĢāļ•āļąāđ‰āļ‡āļ„āđˆāļēāļāđ‡āļ•āļēāļĄ.

6. āļĢāļąāļšāļĄāļ·āļ­āļāļąāļšāļ‚āļ™āļēāļ”āļāļēāļĢāđ€āļ‚āđ‰āļēāļ–āļķāļ‡āļŠāļļāļ”āļ‚āļĩāļ”āļ­āļĒāđˆāļēāļ‡āđ„āļĢ?

āļ‚āļ™āļēāļ”āļāļēāļĢāđ€āļ‚āđ‰āļēāļ–āļķāļ‡ (AX1 āļ–āļķāļ‡ AX5) āļŠāļēāļĄāļēāļĢāļ–āđ€āļžāļīāđˆāļĄāļ‚āļ™āļēāļ”āļ‚āđ‰āļ­āļ„āļ§āļēāļĄāđ€āļ›āđ‡āļ™āļŠāļēāļĄāđ€āļ—āđˆāļē āļ­āļīāļ™āđ€āļ—āļ­āļĢāđŒāđ€āļŸāļ‹āļ•āđ‰āļ­āļ‡āļ›āļĢāļąāļšāļ”āđ‰āļ§āļĒāđ€āļĨāļĒāđŒāđ€āļ­āļēāļ•āđŒāļ­āļ·āđˆāļ™ āđ€āļŠāđˆāļ™ āđ€āļĢāļĩāļĒāļ‡āđāļ™āļ§āļ•āļąāđ‰āļ‡āđāļ—āļ™āđāļ™āļ§āļ™āļ­āļ™ āļ‹āđˆāļ­āļ™āļ­āļ‡āļ„āđŒāļ›āļĢāļ°āļāļ­āļšāļ•āļāđāļ•āđˆāļ‡ āđāļĨāļ°āļ›āļĢāļąāļšāļĢāļ°āļĒāļ°āļŦāđˆāļēāļ‡.

LargeContentSize.swiftswift
// Detect accessibility sizes
struct AdaptiveLayout: View {
    @Environment(\.dynamicTypeSize) var dynamicTypeSize

    var body: some View {
        // Different layout for large sizes
        if dynamicTypeSize.isAccessibilitySize {
            // Vertical layout for large sizes
            VStack(alignment: .leading, spacing: 12) {
                iconAndTitle
                actionButtons
            }
        } else {
            // Standard horizontal layout
            HStack {
                iconAndTitle
                Spacer()
                actionButtons
            }
        }
    }

    var iconAndTitle: some View {
        HStack {
            Image(systemName: "bell.fill")
                .accessibilityHidden(true)  // Decorative, ignore
            Text("Notifications")
        }
    }

    var actionButtons: some View {
        HStack(spacing: 16) {
            Button("Enable") { }
            Button("Settings") { }
        }
    }
}

// In UIKit: observe changes
class AdaptiveViewController: UIViewController {
    override func traitCollectionDidChange(
        _ previousTraitCollection: UITraitCollection?
    ) {
        super.traitCollectionDidChange(previousTraitCollection)

        // Check if size category changed
        if traitCollection.preferredContentSizeCategory !=
           previousTraitCollection?.preferredContentSizeCategory {
            updateLayoutForContentSize()
        }
    }

    func updateLayoutForContentSize() {
        let category = traitCollection.preferredContentSizeCategory

        if category.isAccessibilityCategory {
            // Enable accessible layout
            stackView.axis = .vertical
            decorativeView.isHidden = true
        } else {
            // Standard layout
            stackView.axis = .horizontal
            decorativeView.isHidden = false
        }
    }
}

āļŦāļĄāļ§āļ”āļāļēāļĢāđ€āļ‚āđ‰āļēāļ–āļķāļ‡āđ€āļĢāļīāđˆāļĄāļ—āļĩāđˆ .accessibilityMedium (AX1) āļāļēāļĢāļ­āļ­āļāđāļšāļšāļ„āļ§āļĢāļ„āļģāļ™āļķāļ‡āļ–āļķāļ‡āļāļĢāļ“āļĩāđ€āļŦāļĨāđˆāļēāļ™āļĩāđ‰āļ•āļąāđ‰āļ‡āđāļ•āđˆāļ•āđ‰āļ™.

āļžāļĢāđ‰āļ­āļĄāļ—āļĩāđˆāļˆāļ°āļžāļīāļŠāļīāļ•āļāļēāļĢāļŠāļąāļĄāļ āļēāļĐāļ“āđŒ iOS āđāļĨāđ‰āļ§āļŦāļĢāļ·āļ­āļĒāļąāļ‡āļ„āļĢāļąāļš?

āļāļķāļāļāļ™āļ”āđ‰āļ§āļĒāļ•āļąāļ§āļˆāļģāļĨāļ­āļ‡āđāļšāļšāđ‚āļ•āđ‰āļ•āļ­āļš, flashcards āđāļĨāļ°āđāļšāļšāļ—āļ”āļŠāļ­āļšāđ€āļ—āļ„āļ™āļīāļ„āļ„āļĢāļąāļš

7. āļ—āļģāđƒāļŦāđ‰āļ āļēāļžāđ€āļ‚āđ‰āļēāļ–āļķāļ‡āđ„āļ”āđ‰āļ­āļĒāđˆāļēāļ‡āđ„āļĢ?

āļ āļēāļžāļ•āđ‰āļ­āļ‡āļāļēāļĢāļāļēāļĢāļ”āļđāđāļĨāđāļ•āļāļ•āđˆāļēāļ‡āļāļąāļ™āļ•āļēāļĄāļšāļ—āļšāļēāļ—: āļ āļēāļžāļ‚āđ‰āļ­āļĄāļđāļĨāļ•āđ‰āļ­āļ‡āļĄāļĩāļ„āļģāļ­āļ˜āļīāļšāļēāļĒ āļ āļēāļžāļ›āļĢāļ°āļ”āļąāļšāļ„āļ§āļĢāļ–āļđāļāļ‚āđ‰āļēāļĄ āļ āļēāļžāđ€āļŠāļīāļ‡āđ‚āļ•āđ‰āļ•āļ­āļšāļ•āđ‰āļ­āļ‡āļĄāļĩāļ›āđ‰āļēāļĒāļāļģāļāļąāļšāļāļēāļĢāļāļĢāļ°āļ—āļģ.

AccessibleImages.swiftswift
// Informative image: describe the content
struct ProductImage: View {
    let product: Product

    var body: some View {
        AsyncImage(url: product.imageURL) { image in
            image
                .resizable()
                .accessibilityLabel(product.imageDescription)
                // "Product photo: iPhone 15 Pro, titanium color"
        } placeholder: {
            ProgressView()
                .accessibilityLabel("Loading image")
        }
    }
}

// Decorative image: hide from VoiceOver
struct DecorationView: View {
    var body: some View {
        Image(systemName: "sparkles")
            .accessibilityHidden(true)  // Ignored by VoiceOver
    }
}

// Image button: describe the action, not the image
struct FavoriteButton: View {
    @Binding var isFavorite: Bool

    var body: some View {
        Button {
            isFavorite.toggle()
        } label: {
            Image(systemName: isFavorite ? "heart.fill" : "heart")
        }
        // Don't describe the icon, describe the action
        .accessibilityLabel(isFavorite ? "Remove from favorites" : "Add to favorites")
    }
}

// In UIKit
class ProductImageView: UIImageView {
    override var isAccessibilityElement: Bool {
        get { true }
        set { }
    }

    func configure(with product: Product) {
        image = product.image
        accessibilityLabel = "Photo: \(product.name), \(product.color)"
    }
}

// Image with embedded text (infographic)
class InfographicView: UIImageView {
    override var accessibilityLabel: String? {
        get {
            // Transcribe the textual content of the infographic
            """
            Statistics infographic 2026.
            Growth: 45%.
            Active users: 2.3 million.
            Satisfaction: 4.8 out of 5.
            """
        }
        set { }
    }
}

āļ āļēāļžāļ—āļĩāđˆāļ­āļ˜āļīāļšāļēāļĒāđ„āļĄāđˆāļ”āļĩāļŦāļĢāļ·āļ­āļ āļēāļžāļ›āļĢāļ°āļ”āļąāļšāļ—āļĩāđˆāđ„āļĄāđˆāļ–āļđāļāļ‹āđˆāļ­āļ™āļ—āļģāđƒāļŦāđ‰āļ›āļĢāļ°āļŠāļšāļāļēāļĢāļ“āđŒ VoiceOver āļĨāļ”āļĨāļ‡āļ­āļĒāđˆāļēāļ‡āļĄāļēāļ.

āļāļēāļĢāļˆāļąāļ”āļāļĨāļļāđˆāļĄāđāļĨāļ°āļāļēāļĢāļ™āļģāļ—āļēāļ‡āļ‚āļąāđ‰āļ™āļŠāļđāļ‡

8. āļˆāļąāļ”āļāļĨāļļāđˆāļĄāļ­āļ‡āļ„āđŒāļ›āļĢāļ°āļāļ­āļšāļŠāļģāļŦāļĢāļąāļš VoiceOver āļ­āļĒāđˆāļēāļ‡āđ„āļĢ?

āļāļēāļĢāļˆāļąāļ”āļāļĨāļļāđˆāļĄāļĢāļ§āļĄāļŦāļĨāļēāļĒāļ­āļ‡āļ„āđŒāļ›āļĢāļ°āļāļ­āļšāļ—āļēāļ‡āļŠāļēāļĒāļ•āļēāđ€āļ›āđ‡āļ™āļ­āļ‡āļ„āđŒāļ›āļĢāļ°āļāļ­āļšāđ€āļ”āļĩāļĒāļ§āļ—āļĩāđˆāđ€āļ‚āđ‰āļēāļ–āļķāļ‡āđ„āļ”āđ‰ āļŠāđˆāļ§āļĒāđ€āļĢāđˆāļ‡āļāļēāļĢāļ™āļģāļ—āļēāļ‡āđāļĨāļ°āđƒāļŦāđ‰āļšāļĢāļīāļšāļ—āļ„āļĢāļšāļ–āđ‰āļ§āļ™āđƒāļ™āļ›āļĢāļ°āļāļēāļĻāđ€āļ”āļĩāļĒāļ§.

AccessibilityGrouping.swiftswift
// In SwiftUI: combine children
struct ProductCard: View {
    let product: Product

    var body: some View {
        VStack(alignment: .leading) {
            Text(product.name)
                .font(.headline)
            Text(product.price)
                .font(.subheadline)
            HStack {
                Image(systemName: "star.fill")
                Text(product.rating)
            }
        }
        // Combines all text into a single element
        .accessibilityElement(children: .combine)
        // VoiceOver reads: "iPhone 15 Pro, $1199, 4.8 stars"
    }
}

// In SwiftUI: ignore children and define manually
struct OrderSummary: View {
    let order: Order

    var body: some View {
        HStack {
            Image(systemName: "bag")
            VStack(alignment: .leading) {
                Text(order.itemCount)
                Text(order.total)
            }
            Image(systemName: "chevron.right")
        }
        // Ignore children hierarchy
        .accessibilityElement(children: .ignore)
        // Define custom label
        .accessibilityLabel("Order of \(order.itemCount) items for \(order.total)")
        .accessibilityHint("Double-tap to view details")
        .accessibilityAddTraits(.isButton)
    }
}

// In UIKit: accessible container
class ProductCardView: UIView {
    let nameLabel = UILabel()
    let priceLabel = UILabel()
    let ratingLabel = UILabel()

    override var isAccessibilityElement: Bool {
        get { true }  // The container is the accessible element
        set { }
    }

    override var accessibilityLabel: String? {
        get {
            // Combine information
            "\(nameLabel.text ?? ""), \(priceLabel.text ?? ""), \(ratingLabel.text ?? "")"
        }
        set { }
    }
}

āļŦāļēāļāđ„āļĄāđˆāļˆāļąāļ”āļāļĨāļļāđˆāļĄ VoiceOver āļˆāļ°āļŦāļĒāļļāļ”āļ—āļĩāđˆ Text āđāļ•āđˆāļĨāļ°āļ•āļąāļ§āđāļĒāļāļāļąāļ™ āļ—āļģāđƒāļŦāđ‰āļ™āļģāļ—āļēāļ‡āļŠāđ‰āļēāļĨāļ‡āļ­āļĒāđˆāļēāļ‡āđ€āļŦāđ‡āļ™āđ„āļ”āđ‰āļŠāļąāļ”.

9. āļ—āļģāļāļēāļĢāļāļĢāļ°āļ—āļģāļāļēāļĢāđ€āļ‚āđ‰āļēāļ–āļķāļ‡āđāļšāļšāļāļģāļŦāļ™āļ”āđ€āļ­āļ‡āļ­āļĒāđˆāļēāļ‡āđ„āļĢ?

āļāļēāļĢāļāļĢāļ°āļ—āļģāđāļšāļšāļāļģāļŦāļ™āļ”āđ€āļ­āļ‡āđ€āļžāļīāđˆāļĄāļŸāļąāļ‡āļāđŒāļŠāļąāļ™āļ—āļĩāđˆāđ€āļ‚āđ‰āļēāļ–āļķāļ‡āđ„āļ”āđ‰āđ‚āļ”āļĒāđ„āļĄāđˆāļ—āļģāđƒāļŦāđ‰āļ­āļīāļ™āđ€āļ—āļ­āļĢāđŒāđ€āļŸāļ‹āļĢāļ āļœāļđāđ‰āđƒāļŠāđ‰ VoiceOver āđ€āļ‚āđ‰āļēāļ–āļķāļ‡āļœāđˆāļēāļ™āļāļēāļĢāļ›āļąāļ”āđāļ™āļ§āļ•āļąāđ‰āļ‡.

CustomAccessibilityActions.swiftswift
// In SwiftUI: custom actions
struct EmailRow: View {
    let email: Email
    let onArchive: () -> Void
    let onDelete: () -> Void
    let onFlag: () -> Void

    var body: some View {
        HStack {
            VStack(alignment: .leading) {
                Text(email.sender)
                    .font(.headline)
                Text(email.subject)
                    .font(.subheadline)
            }
            Spacer()
            if email.isFlagged {
                Image(systemName: "flag.fill")
            }
        }
        .accessibilityElement(children: .combine)
        .accessibilityLabel("\(email.sender), \(email.subject)")
        // Actions accessible via vertical swipe
        .accessibilityAction(named: "Archive") {
            onArchive()
        }
        .accessibilityAction(named: "Delete") {
            onDelete()
        }
        .accessibilityAction(named: email.isFlagged ? "Remove flag" : "Flag") {
            onFlag()
        }
    }
}

// In UIKit: override accessibilityCustomActions
class EmailCell: UITableViewCell {
    var email: Email!
    var onArchive: (() -> Void)?
    var onDelete: (() -> Void)?

    override var accessibilityCustomActions: [UIAccessibilityCustomAction]? {
        get {
            [
                UIAccessibilityCustomAction(
                    name: "Archive",
                    target: self,
                    selector: #selector(archiveAction)
                ),
                UIAccessibilityCustomAction(
                    name: "Delete",
                    target: self,
                    selector: #selector(deleteAction)
                )
            ]
        }
        set { }
    }

    @objc private func archiveAction() -> Bool {
        onArchive?()
        return true  // true = action succeeded
    }

    @objc private func deleteAction() -> Bool {
        onDelete?()
        return true
    }
}

āļāļēāļĢāļāļĢāļ°āļ—āļģāđāļšāļšāļāļģāļŦāļ™āļ”āđ€āļ­āļ‡āļ—āļ”āđāļ—āļ™āļ—āđˆāļēāļ—āļēāļ‡āļ›āļąāļ”āļ—āļĩāđˆāļ„āđ‰āļ™āļŦāļēāđ„āļ”āđ‰āļĒāļēāļāļ”āđ‰āļ§āļĒ VoiceOver āđ„āļ”āđ‰āļ­āļĒāđˆāļēāļ‡āļĄāļĩāļ›āļĢāļ°āļŠāļīāļ—āļ˜āļīāļ āļēāļž.

10. āļˆāļąāļ”āļāļēāļĢāđ‚āļŸāļāļąāļŠāļ‚āļ­āļ‡ VoiceOver āđāļšāļšāđ‚āļ›āļĢāđāļāļĢāļĄāļ­āļĒāđˆāļēāļ‡āđ„āļĢ?

āļāļēāļĢāļ„āļ§āļšāļ„āļļāļĄāđ‚āļŸāļāļąāļŠāļŠāļģāļ„āļąāļāļŦāļĨāļąāļ‡āļˆāļēāļāļāļēāļĢāđ€āļ›āļĨāļĩāđˆāļĒāļ™āđāļ›āļĨāļ‡āļ­āļīāļ™āđ€āļ—āļ­āļĢāđŒāđ€āļŸāļ‹: āļŦāļ™āđ‰āļēāļ•āđˆāļēāļ‡āđ‚āļĄāļ”āļ­āļĨāļ›āļĢāļēāļāļ āļāļēāļĢāđ‚āļŦāļĨāļ”āđ€āļ™āļ·āđ‰āļ­āļŦāļē āļāļēāļĢāļ™āļģāļ—āļēāļ‡ āđ‚āļŸāļāļąāļŠāļ„āļ§āļĢāļ™āļģāļ—āļēāļ‡āļœāļđāđ‰āđƒāļŠāđ‰āđ„āļ›āļĒāļąāļ‡āļ‚āđ‰āļ­āļĄāļđāļĨāļ—āļĩāđˆāđ€āļāļĩāđˆāļĒāļ§āļ‚āđ‰āļ­āļ‡.

AccessibilityFocus.swiftswift
// In SwiftUI with @AccessibilityFocusState (iOS 15+)
struct SearchView: View {
    @State private var searchText = ""
    @State private var results: [Result] = []
    @AccessibilityFocusState private var focusedResult: Result?

    var body: some View {
        VStack {
            TextField("Search", text: $searchText)
                .onSubmit {
                    performSearch()
                }

            List(results) { result in
                ResultRow(result: result)
                    .accessibilityFocused($focusedResult, equals: result)
            }
        }
    }

    func performSearch() {
        Task {
            results = await api.search(searchText)
            // Move focus to first result
            if let first = results.first {
                focusedResult = first
            }
        }
    }
}

// In UIKit: post a notification
class ModalViewController: UIViewController {
    @IBOutlet weak var titleLabel: UILabel!

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        // Announce and move focus
        UIAccessibility.post(
            notification: .screenChanged,  // New screen
            argument: titleLabel           // Element to focus
        )
    }

    func showError(_ message: String) {
        errorLabel.text = message
        errorLabel.isHidden = false

        // Announce layout change
        UIAccessibility.post(
            notification: .layoutChanged,  // Change in current screen
            argument: errorLabel
        )
    }

    func showLoadingComplete() {
        // Announce without moving focus
        UIAccessibility.post(
            notification: .announcement,
            argument: "Loading complete"
        )
    }
}

āļāļēāļĢāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™ .screenChanged āđāļĨāļ° .layoutChanged āđāļˆāđ‰āļ‡ VoiceOver āļ–āļķāļ‡āļāļēāļĢāđ€āļ›āļĨāļĩāđˆāļĒāļ™āđāļ›āļĨāļ‡āļŠāļģāļ„āļąāļāđƒāļ™āļ­āļīāļ™āđ€āļ—āļ­āļĢāđŒāđ€āļŸāļ‹.

āļžāļĢāđ‰āļ­āļĄāļ—āļĩāđˆāļˆāļ°āļžāļīāļŠāļīāļ•āļāļēāļĢāļŠāļąāļĄāļ āļēāļĐāļ“āđŒ iOS āđāļĨāđ‰āļ§āļŦāļĢāļ·āļ­āļĒāļąāļ‡āļ„āļĢāļąāļš?

āļāļķāļāļāļ™āļ”āđ‰āļ§āļĒāļ•āļąāļ§āļˆāļģāļĨāļ­āļ‡āđāļšāļšāđ‚āļ•āđ‰āļ•āļ­āļš, flashcards āđāļĨāļ°āđāļšāļšāļ—āļ”āļŠāļ­āļšāđ€āļ—āļ„āļ™āļīāļ„āļ„āļĢāļąāļš

āļ­āļ‡āļ„āđŒāļ›āļĢāļ°āļāļ­āļšāļ—āļĩāđˆāļ›āļĢāļąāļšāđ„āļ”āđ‰āđāļĨāļ°āļāļēāļĢāļ„āļ§āļšāļ„āļļāļĄāļ—āļĩāđˆāļ‹āļąāļšāļ‹āđ‰āļ­āļ™

11. āļ—āļģāđƒāļŦāđ‰ slider āđāļšāļšāļāļģāļŦāļ™āļ”āđ€āļ­āļ‡āđ€āļ‚āđ‰āļēāļ–āļķāļ‡āđ„āļ”āđ‰āļ­āļĒāđˆāļēāļ‡āđ„āļĢ?

Slider āđāļšāļšāļāļģāļŦāļ™āļ”āđ€āļ­āļ‡āļ•āđ‰āļ­āļ‡āļ™āļģ trait .adjustable āļĄāļēāđƒāļŠāđ‰āđāļĨāļ°āļ•āļ­āļšāļŠāļ™āļ­āļ‡āļ•āđˆāļ­āļ—āđˆāļēāļ—āļēāļ‡āđ€āļžāļīāđˆāļĄ/āļĨāļ” VoiceOver āđƒāļŠāđ‰āļāļēāļĢāļ›āļąāļ”āđāļ™āļ§āļ•āļąāđ‰āļ‡āđ€āļžāļ·āđˆāļ­āđ€āļ›āļĨāļĩāđˆāļĒāļ™āļ„āđˆāļē.

AccessibleSlider.swiftswift
// In SwiftUI: Slider is accessible by default
struct VolumeControl: View {
    @Binding var volume: Double

    var body: some View {
        Slider(value: $volume, in: 0...100, step: 5)
            .accessibilityLabel("Volume")
            .accessibilityValue("\(Int(volume)) percent")
    }
}

// Custom accessible slider
struct CustomRatingSlider: View {
    @Binding var rating: Int

    var body: some View {
        HStack {
            ForEach(1...5, id: \.self) { star in
                Image(systemName: star <= rating ? "star.fill" : "star")
                    .onTapGesture { rating = star }
            }
        }
        .accessibilityElement(children: .ignore)
        .accessibilityLabel("Rating")
        .accessibilityValue("\(rating) star\(rating > 1 ? "s" : "") out of 5")
        .accessibilityAdjustableAction { direction in
            switch direction {
            case .increment:
                rating = min(5, rating + 1)
            case .decrement:
                rating = max(1, rating - 1)
            @unknown default:
                break
            }
        }
    }
}

// In UIKit: adjustable trait
class StarRatingView: UIView {
    var rating: Int = 3 {
        didSet {
            updateStars()
            // Announce the new value
            UIAccessibility.post(
                notification: .announcement,
                argument: accessibilityValue
            )
        }
    }

    override var accessibilityTraits: UIAccessibilityTraits {
        get { .adjustable }  // Enables adjustment gestures
        set { }
    }

    override var accessibilityLabel: String? {
        get { "Rating" }
        set { }
    }

    override var accessibilityValue: String? {
        get { "\(rating) stars out of 5" }
        set { }
    }

    // Called on swipe up
    override func accessibilityIncrement() {
        rating = min(5, rating + 1)
    }

    // Called on swipe down
    override func accessibilityDecrement() {
        rating = max(1, rating - 1)
    }
}

āļŦāļēāļāđ„āļĄāđˆāļĄāļĩ trait .adjustable slider āđāļšāļšāļāļģāļŦāļ™āļ”āđ€āļ­āļ‡āļˆāļ°āļšāļąāļ‡āļ„āļąāļšāđƒāļŦāđ‰āļœāļđāđ‰āđƒāļŠāđ‰āđāļ•āļ°āļ—āļĩāđˆāļ”āļēāļ§āđāļ•āđˆāļĨāļ°āļ”āļ§āļ‡āļ—āļĩāļĨāļ°āļ”āļ§āļ‡.

Carousel āļĄāļĩāļ„āļ§āļēāļĄāļ—āđ‰āļēāļ—āļēāļĒāļ”āđ‰āļēāļ™āļāļēāļĢāđ€āļ‚āđ‰āļēāļ–āļķāļ‡: āļāļēāļĢāļ™āļģāļ—āļēāļ‡āļ—āļĩāđˆāđ„āļĄāđˆāđ€āļ›āđ‡āļ™āđ€āļŠāđ‰āļ™āļ•āļĢāļ‡ āđ€āļ™āļ·āđ‰āļ­āļŦāļēāļ™āļ­āļāļŦāļ™āđ‰āļēāļˆāļ­ āđāļĨāļ°āļŦāļĨāļēāļĒāļŠāļ–āļēāļ™āļ° āļ—āļēāļ‡āļ­āļ­āļāļ„āļ·āļ­āļœāļŠāļēāļ™ trait .adjustable āļāļąāļšāļāļēāļĢāļ›āļĢāļ°āļāļēāļĻāļ•āļēāļĄāļšāļĢāļīāļšāļ—.

AccessibleCarousel.swiftswift
struct ImageCarousel: View {
    let images: [CarouselImage]
    @State private var currentIndex = 0

    var body: some View {
        TabView(selection: $currentIndex) {
            ForEach(images.indices, id: \.self) { index in
                CarouselItem(image: images[index])
                    .tag(index)
            }
        }
        .tabViewStyle(.page)
        .accessibilityElement(children: .contain)
        .accessibilityLabel("Image carousel")
        .accessibilityValue("Image \(currentIndex + 1) of \(images.count)")
        .accessibilityHint("Swipe to change image")
        .accessibilityAdjustableAction { direction in
            withAnimation {
                switch direction {
                case .increment:
                    currentIndex = min(images.count - 1, currentIndex + 1)
                case .decrement:
                    currentIndex = max(0, currentIndex - 1)
                @unknown default:
                    break
                }
            }
        }
    }
}

// In UIKit with UIPageViewController
class CarouselAccessibilityContainer: UIView {
    var currentPage = 0
    var totalPages = 5

    override var isAccessibilityElement: Bool {
        get { true }
        set { }
    }

    override var accessibilityTraits: UIAccessibilityTraits {
        get { .adjustable }
        set { }
    }

    override var accessibilityLabel: String? {
        get { "Promotional carousel" }
        set { }
    }

    override var accessibilityValue: String? {
        get { "Page \(currentPage + 1) of \(totalPages)" }
        set { }
    }

    override func accessibilityIncrement() {
        guard currentPage < totalPages - 1 else { return }
        currentPage += 1
        delegate?.scrollToPage(currentPage)
    }

    override func accessibilityDecrement() {
        guard currentPage > 0 else { return }
        currentPage -= 1
        delegate?.scrollToPage(currentPage)
    }
}

āļ§āļīāļ˜āļĩāļ™āļĩāđ‰āļ—āļģāđƒāļŦāđ‰āļ™āļģāļ—āļēāļ‡ carousel āđ„āļ”āđ‰āđ‚āļ”āļĒāđ„āļĄāđˆāļžāļķāđˆāļ‡āļ—āđˆāļēāļ—āļēāļ‡āļāļēāļĢāļ›āļąāļ”āļĄāļēāļ•āļĢāļāļēāļ™.

13. āļˆāļąāļ”āļāļēāļĢāļāļēāļĢāđ€āļ‚āđ‰āļēāļ–āļķāļ‡āļ‚āļ­āļ‡āļŸāļ­āļĢāđŒāļĄāļ­āļĒāđˆāļēāļ‡āđ„āļĢ?

āļŸāļ­āļĢāđŒāļĄāļ—āļĩāđˆāđ€āļ‚āđ‰āļēāļ–āļķāļ‡āđ„āļ”āđ‰āļˆāļ°āđ€āļŠāļ·āđˆāļ­āļĄāđ‚āļĒāļ‡āđāļ•āđˆāļĨāļ°āļŠāđˆāļ­āļ‡āļāļąāļš label āļ‚āļ­āļ‡āļĄāļąāļ™ āļšāļ­āļāļ‚āđ‰āļ­āļœāļīāļ”āļžāļĨāļēāļ”āļ­āļĒāđˆāļēāļ‡āļŠāļąāļ”āđ€āļˆāļ™ āđāļĨāļ°āļĒāļ­āļĄāđƒāļŦāđ‰āļ™āļģāļ—āļēāļ‡āļĢāļ°āļŦāļ§āđˆāļēāļ‡āļŠāđˆāļ­āļ‡āļ­āļĒāđˆāļēāļ‡āļĢāļēāļšāļĢāļ·āđˆāļ™.

AccessibleForms.swiftswift
struct RegistrationForm: View {
    @State private var email = ""
    @State private var password = ""
    @State private var emailError: String?
    @State private var passwordError: String?

    var body: some View {
        Form {
            Section {
                TextField("Email", text: $email)
                    .textContentType(.emailAddress)
                    .keyboardType(.emailAddress)
                    .accessibilityLabel("Email address")
                    .accessibilityHint(emailError != nil ? "Error: \(emailError!)" : nil)

                if let error = emailError {
                    Text(error)
                        .foregroundColor(.red)
                        .font(.caption)
                        .accessibilityLabel("Email error: \(error)")
                }
            }

            Section {
                SecureField("Password", text: $password)
                    .textContentType(.newPassword)
                    .accessibilityLabel("Password")
                    .accessibilityHint(passwordHint)

                PasswordStrengthIndicator(password: password)
            }

            Button("Create account") {
                submitForm()
            }
            .disabled(!isFormValid)
            .accessibilityHint(isFormValid ? "Double-tap to create your account" : "Form incomplete")
        }
    }

    var passwordHint: String {
        if let error = passwordError {
            return "Error: \(error)"
        }
        return "Minimum 8 characters with uppercase and numbers"
    }
}

// Accessible password strength indicator
struct PasswordStrengthIndicator: View {
    let password: String

    var strength: PasswordStrength {
        // Calculation logic
        PasswordStrength.calculate(password)
    }

    var body: some View {
        HStack(spacing: 4) {
            ForEach(0..<4) { index in
                Rectangle()
                    .fill(index < strength.level ? strength.color : Color.gray.opacity(0.3))
                    .frame(height: 4)
            }
        }
        .accessibilityElement(children: .ignore)
        .accessibilityLabel("Password strength")
        .accessibilityValue(strength.description)  // "Weak", "Medium", "Strong", "Very strong"
    }
}

āļ„āļģāđāļ™āļ°āļ™āļģāļāļēāļĢāđ€āļ‚āđ‰āļēāļ–āļķāļ‡āļŠāļ·āđˆāļ­āļŠāļēāļĢāļ‚āđ‰āļ­āļœāļīāļ”āļžāļĨāļēāļ”āđāļĨāļ°āļ„āļģāđāļ™āļ°āļ™āļģāđ‚āļ”āļĒāđ„āļĄāđˆāļ—āļģāđƒāļŦāđ‰āļ­āļīāļ™āđ€āļ—āļ­āļĢāđŒāđ€āļŸāļ‹āļĢāļ.

āļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļšāđāļĨāļ°āļ—āļ”āļŠāļ­āļšāļāļēāļĢāđ€āļ‚āđ‰āļēāļ–āļķāļ‡

14. āļ•āļĢāļ§āļˆāļŠāļ­āļšāļāļēāļĢāđ€āļ‚āđ‰āļēāļ–āļķāļ‡āļ‚āļ­āļ‡āđāļ­āļ› iOS āļ­āļĒāđˆāļēāļ‡āđ„āļĢ?

āļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļšāļĢāļ§āļĄāđ€āļ„āļĢāļ·āđˆāļ­āļ‡āļĄāļ·āļ­āļ­āļąāļ•āđ‚āļ™āļĄāļąāļ•āļīāđāļĨāļ°āļāļēāļĢāļ—āļ”āļŠāļ­āļšāļ”āđ‰āļ§āļĒāļĄāļ·āļ­ Accessibility Inspector āļ‚āļ­āļ‡ Xcode āļ§āļīāđ€āļ„āļĢāļēāļ°āļŦāđŒāļ­āļ‡āļ„āđŒāļ›āļĢāļ°āļāļ­āļš VoiceOver āļ•āļĢāļ§āļˆāļŠāļ­āļšāļ›āļĢāļ°āļŠāļšāļāļēāļĢāļ“āđŒāļˆāļĢāļīāļ‡ āđāļĨāļ° unit test āļ•āļĢāļ§āļˆāļŠāļ­āļšāļ„āļļāļ“āļŠāļĄāļšāļąāļ•āļīāļāļēāļĢāđ€āļ‚āđ‰āļēāļ–āļķāļ‡.

AccessibilityAudit.swiftswift
// Unit test accessibility properties
import XCTest
@testable import MyApp

class AccessibilityTests: XCTestCase {

    func testProductCardAccessibility() {
        let product = Product(name: "iPhone", price: "$999", rating: "4.5")
        let view = ProductCardView(product: product)

        // Verify element is accessible
        XCTAssertTrue(view.isAccessibilityElement)

        // Verify label
        XCTAssertEqual(
            view.accessibilityLabel,
            "iPhone, $999, 4.5 stars"
        )

        // Verify traits
        XCTAssertTrue(view.accessibilityTraits.contains(.button))
    }

    func testDynamicTypeSupport() {
        let label = UILabel()
        label.font = UIFont.preferredFont(forTextStyle: .body)

        // Verify automatic adjustment
        XCTAssertTrue(label.adjustsFontForContentSizeCategory)
    }
}

// UI test with simulated VoiceOver
class AccessibilityUITests: XCTestCase {

    func testLoginFlowAccessibility() {
        let app = XCUIApplication()
        app.launch()

        // Access elements by accessibility label
        let emailField = app.textFields["Email address"]
        XCTAssertTrue(emailField.exists)

        emailField.tap()
        emailField.typeText("test@example.com")

        let passwordField = app.secureTextFields["Password"]
        XCTAssertTrue(passwordField.exists)

        let loginButton = app.buttons["Sign in"]
        XCTAssertTrue(loginButton.exists)
        XCTAssertTrue(loginButton.isEnabled)
    }

    // Automatic audit iOS 17+
    func testAccessibilityAudit() throws {
        let app = XCUIApplication()
        app.launch()

        // Automatic screen audit
        try app.performAccessibilityAudit()
    }
}

āļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļšāļ­āļąāļ•āđ‚āļ™āļĄāļąāļ•āļīāļ‚āļ­āļ‡ iOS 17+ āļ•āļĢāļ§āļˆāļžāļšāļ›āļąāļāļŦāļēāļ—āļąāđˆāļ§āđ„āļ›: label āļŦāļēāļĒ āļ„āļ­āļ™āļ—āļĢāļēāļŠāļ•āđŒāđ„āļĄāđˆāļžāļ­ āļ­āļ‡āļ„āđŒāļ›āļĢāļ°āļāļ­āļšāđ€āļĨāđ‡āļāđ€āļāļīāļ™āđ„āļ›.

15. āļ‚āđ‰āļ­āļœāļīāļ”āļžāļĨāļēāļ”āļ”āđ‰āļēāļ™āļāļēāļĢāđ€āļ‚āđ‰āļēāļ–āļķāļ‡āļ—āļĩāđˆāļžāļšāļšāđˆāļ­āļĒāļ—āļĩāđˆāļŠāļļāļ”āļĄāļĩāļ­āļ°āđ„āļĢāļšāđ‰āļēāļ‡?

āļ‚āđ‰āļ­āļœāļīāļ”āļžāļĨāļēāļ”āļ—āļĩāđˆāļžāļšāļšāđˆāļ­āļĒāļĢāļ§āļĄāļ–āļķāļ‡ label āļŦāļēāļĒ āļ āļēāļžāđ„āļĄāđˆāļĄāļĩāļ„āļģāļ­āļ˜āļīāļšāļēāļĒ āļ‚āļēāļ”āļāļēāļĢāļĢāļ­āļ‡āļĢāļąāļš Dynamic Type āđāļĨāļ°āļ­āļ‡āļ„āđŒāļ›āļĢāļ°āļāļ­āļšāđ€āļŠāļīāļ‡āđ‚āļ•āđ‰āļ•āļ­āļšāļ—āļĩāđˆāļ‚āļēāļ” traits āļ—āļĩāđˆāđ€āļŦāļĄāļēāļ°āļŠāļĄ.

CommonMistakes.swiftswift
// WRONG: Button without accessible label
Button {
    performAction()
} label: {
    Image(systemName: "plus")
}
// VoiceOver reads: "Button" - useless

// CORRECT: Explicit label
Button {
    performAction()
} label: {
    Image(systemName: "plus")
}
.accessibilityLabel("Add item")


// WRONG: Fixed font size
Text("Important")
    .font(.system(size: 16))  // Doesn't respect Dynamic Type

// CORRECT: Semantic text style
Text("Important")
    .font(.body)  // Adapts to preferences


// WRONG: Informative image without description
Image("product-photo")

// CORRECT: Content description
Image("product-photo")
    .accessibilityLabel("Product photo: Black audio headphones")


// WRONG: Clickable element without button trait
Text("Learn more")
    .onTapGesture { showDetails() }
// VoiceOver doesn't know it's interactive

// CORRECT: Appropriate traits
Text("Learn more")
    .onTapGesture { showDetails() }
    .accessibilityAddTraits(.isButton)
    .accessibilityHint("Double-tap to show details")


// WRONG: Insufficient contrast
Text("Light gray text")
    .foregroundColor(.gray.opacity(0.4))

// CORRECT: Minimum contrast ratio 4.5:1
Text("Readable text")
    .foregroundColor(.secondary)  // Respects guidelines


// WRONG: Touch target too small
Button { } label: {
    Image(systemName: "xmark")
        .font(.caption)  // Too small (< 44x44 points)
}

// CORRECT: Minimum 44x44 point area
Button { } label: {
    Image(systemName: "xmark")
}
.frame(minWidth: 44, minHeight: 44)

āļ‚āđ‰āļ­āļœāļīāļ”āļžāļĨāļēāļ”āđ€āļŦāļĨāđˆāļēāļ™āļĩāđ‰āļŠāļēāļĄāļēāļĢāļ–āļ•āļĢāļ§āļˆāļžāļšāļ”āđ‰āļ§āļĒ Accessibility Inspector āđāļĨāļ°āđāļāđ‰āđ„āļ‚āļ”āđ‰āļ§āļĒāđ‚āļ„āđ‰āļ”āđ€āļžāļĩāļĒāļ‡āđ„āļĄāđˆāļāļĩāđˆāļšāļĢāļĢāļ—āļąāļ”.

āļŠāļĢāļļāļ›

āļāļēāļĢāđ€āļ‚āđ‰āļēāļ–āļķāļ‡āļšāļ™ iOS āļ•āļąāđ‰āļ‡āļ­āļĒāļđāđˆāļšāļ™āļŠāļēāļĄāđ€āļŠāļēāļŦāļĨāļąāļ: VoiceOver āļŠāļģāļŦāļĢāļąāļšāļœāļđāđ‰āļžāļīāļāļēāļĢāļ—āļēāļ‡āļŠāļēāļĒāļ•āļē Dynamic Type āļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļ›āļĢāļąāļšāļ‚āđ‰āļ­āļ„āļ§āļēāļĄ āđāļĨāļ° traits āđ€āļŠāļīāļ‡āļ„āļ§āļēāļĄāļŦāļĄāļēāļĒāļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļ™āļģāļ—āļēāļ‡āļ—āļĩāđˆāļŠāļĄāđˆāļģāđ€āļŠāļĄāļ­ āļāļēāļĢāđ€āļ‚āđ‰āļēāđƒāļˆāđāļ™āļ§āļ„āļīāļ”āđ€āļŦāļĨāđˆāļēāļ™āļĩāđ‰āđāļŠāļ”āļ‡āļ–āļķāļ‡āļ„āļ§āļēāļĄāđ€āļŠāļĩāđˆāļĒāļ§āļŠāļēāļāļ—āļĩāđˆāļ™āļēāļĒāļˆāđ‰āļēāļ‡āļĄāļ­āļ‡āļŦāļēāļ­āļĒāđˆāļēāļ‡āļˆāļĢāļīāļ‡āļˆāļąāļ‡.

āļĢāļēāļĒāļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļš

  • āļ•āļąāđ‰āļ‡āļ„āđˆāļē VoiceOver āļ”āđ‰āļ§āļĒ label, hint āđāļĨāļ° traits āļ—āļĩāđˆāđ€āļŦāļĄāļēāļ°āļŠāļĄ
  • āđƒāļŠāđ‰āļ‡āļēāļ™ Dynamic Type āļ”āđ‰āļ§āļĒāļŠāđ„āļ•āļĨāđŒāļ‚āđ‰āļ­āļ„āļ§āļēāļĄāđ€āļŠāļīāļ‡āļ„āļ§āļēāļĄāļŦāļĄāļēāļĒ
  • āļ›āļĢāļąāļšāđ€āļĨāļĒāđŒāđ€āļ­āļēāļ•āđŒāļŠāļģāļŦāļĢāļąāļšāļ‚āļ™āļēāļ”āļāļēāļĢāđ€āļ‚āđ‰āļēāļ–āļķāļ‡āļŠāļļāļ”āļ‚āļĩāļ”
  • āļˆāļąāļ”āļāļĨāļļāđˆāļĄāļ­āļ‡āļ„āđŒāļ›āļĢāļ°āļāļ­āļšāđ€āļžāļ·āđˆāļ­āļ™āļģāļ—āļēāļ‡āļ­āļĒāđˆāļēāļ‡āļĄāļĩāļ›āļĢāļ°āļŠāļīāļ—āļ˜āļīāļ āļēāļž
  • āļˆāļąāļ”āļāļēāļĢāđ‚āļŸāļāļąāļŠāļ”āđ‰āļ§āļĒāđ‚āļ›āļĢāđāļāļĢāļĄāļŦāļĨāļąāļ‡āļāļēāļĢāđ€āļ›āļĨāļĩāđˆāļĒāļ™āđāļ›āļĨāļ‡āļ­āļīāļ™āđ€āļ—āļ­āļĢāđŒāđ€āļŸāļ‹
  • āļ—āļģāđƒāļŦāđ‰āļāļēāļĢāļ„āļ§āļšāļ„āļļāļĄāđāļšāļšāļāļģāļŦāļ™āļ”āđ€āļ­āļ‡āļŠāļēāļĄāļēāļĢāļ–āļ›āļĢāļąāļšāļ„āđˆāļēāđ„āļ”āđ‰
  • āļ•āļĢāļ§āļˆāļŠāļ­āļšāđ€āļ›āđ‡āļ™āļ›āļĢāļ°āļˆāļģāļ”āđ‰āļ§āļĒ Accessibility Inspector āđāļĨāļ°āļ—āļ”āļŠāļ­āļšāļ­āļąāļ•āđ‚āļ™āļĄāļąāļ•āļī
  • āļŦāļĨāļĩāļāđ€āļĨāļĩāđˆāļĒāļ‡āļ‚āđ‰āļ­āļœāļīāļ”āļžāļĨāļēāļ”āļ—āļąāđˆāļ§āđ„āļ›: label āļŦāļēāļĒ āļ āļēāļžāđ„āļĄāđˆāļĄāļĩāļ„āļģāļ­āļ˜āļīāļšāļēāļĒ āļ„āļ­āļ™āļ—āļĢāļēāļŠāļ•āđŒāđ„āļĄāđˆāļžāļ­

āđāļŦāļĨāđˆāļ‡āļ‚āđ‰āļ­āļĄāļđāļĨāđ€āļžāļīāđˆāļĄāđ€āļ•āļīāļĄ

āđ€āļ­āļāļŠāļēāļĢāļ—āļēāļ‡āļāļēāļĢāļ‚āļ­āļ‡ Apple "Human Interface Guidelines - Accessibility" āļĒāļąāļ‡āļ„āļ‡āđ€āļ›āđ‡āļ™āđāļŦāļĨāđˆāļ‡āļ­āđ‰āļēāļ‡āļ­āļīāļ‡āļŦāļĨāļąāļ āļāļēāļĢāļ—āļ”āļŠāļ­āļšāļšāđˆāļ­āļĒāļ„āļĢāļąāđ‰āļ‡āđ‚āļ”āļĒāđ€āļ›āļīāļ” VoiceOver āļŠāđˆāļ§āļĒāļžāļšāļ›āļąāļāļŦāļēāļ—āļĩāđˆāđ€āļ„āļĢāļ·āđˆāļ­āļ‡āļĄāļ·āļ­āļ­āļąāļ•āđ‚āļ™āļĄāļąāļ•āļīāđ„āļĄāđˆāļŠāļēāļĄāļēāļĢāļ–āļˆāļąāļšāđ„āļ”āđ‰.

āđ€āļĢāļīāđˆāļĄāļāļķāļāļ‹āđ‰āļ­āļĄāđ€āļĨāļĒ!

āļ—āļ”āļŠāļ­āļšāļ„āļ§āļēāļĄāļĢāļđāđ‰āļ‚āļ­āļ‡āļ„āļļāļ“āļ”āđ‰āļ§āļĒāļ•āļąāļ§āļˆāļģāļĨāļ­āļ‡āļŠāļąāļĄāļ āļēāļĐāļ“āđŒāđāļĨāļ°āđāļšāļšāļ—āļ”āļŠāļ­āļšāđ€āļ—āļ„āļ™āļīāļ„āļ„āļĢāļąāļš

āđāļ—āđ‡āļ

#ios
#accessibility
#voiceover
#dynamic-type
#swift

āđāļŠāļĢāđŒ

āļšāļ—āļ„āļ§āļēāļĄāļ—āļĩāđˆāđ€āļāļĩāđˆāļĒāļ§āļ‚āđ‰āļ­āļ‡

āļāļēāļĢāļĒāđ‰āļēāļĒāļˆāļēāļ Combine āđ„āļ›āļĒāļąāļ‡ async/await āđƒāļ™ Swift āļžāļĢāđ‰āļ­āļĄāļĢāļđāļ›āđāļšāļšāļāļēāļĢāļ­āļĒāļđāđˆāļĢāđˆāļ§āļĄāļāļąāļ™

Combine vs async/await āđƒāļ™ Swift: āļĢāļđāļ›āđāļšāļšāļāļēāļĢāļĒāđ‰āļēāļĒāļĢāļ°āļšāļšāđāļšāļšāļ„āđˆāļ­āļĒāđ€āļ›āđ‡āļ™āļ„āđˆāļ­āļĒāđ„āļ›

āļ„āļđāđˆāļĄāļ·āļ­āļ‰āļšāļąāļšāļŠāļĄāļšāļđāļĢāļ“āđŒāļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļĒāđ‰āļēāļĒāļˆāļēāļ Combine āđ„āļ›āļĒāļąāļ‡ async/await āđƒāļ™ Swift: āļāļĨāļĒāļļāļ—āļ˜āđŒāđāļšāļšāļ„āđˆāļ­āļĒāđ€āļ›āđ‡āļ™āļ„āđˆāļ­āļĒāđ„āļ› āļĢāļđāļ›āđāļšāļšāļāļēāļĢāđ€āļŠāļ·āđˆāļ­āļĄāđ‚āļĒāļ‡ āđāļĨāļ°āļāļēāļĢāļ­āļĒāļđāđˆāļĢāđˆāļ§āļĄāļāļąāļ™āļ‚āļ­āļ‡āļāļĢāļ°āļšāļ§āļ™āļ—āļąāļĻāļ™āđŒāđƒāļ™āđ‚āļ„āđ‰āļ”āđ€āļšāļŠ iOS

Metaprogramming āļ”āđ‰āļ§āļĒ Swift Macros āđāļĨāļ°āļ•āļąāļ§āļ­āļĒāđˆāļēāļ‡āđ‚āļ„āđ‰āļ”āđ€āļŠāļīāļ‡āļ›āļāļīāļšāļąāļ•āļī

Swift Macros: āļ•āļąāļ§āļ­āļĒāđˆāļēāļ‡āđ€āļŠāļīāļ‡āļ›āļāļīāļšāļąāļ•āļīāļ‚āļ­āļ‡ metaprogramming

āļ„āļđāđˆāļĄāļ·āļ­āļ‰āļšāļąāļšāļŠāļĄāļšāļđāļĢāļ“āđŒāļ‚āļ­āļ‡ Swift Macros: āļāļēāļĢāļŠāļĢāđ‰āļēāļ‡āļĄāļēāđ‚āļ„āļĢ freestanding āđāļĨāļ° attached, āļāļēāļĢāļˆāļąāļ”āļāļēāļĢ AST āļ”āđ‰āļ§āļĒ swift-syntax āđāļĨāļ°āļ•āļąāļ§āļ­āļĒāđˆāļēāļ‡āđ€āļŠāļīāļ‡āļ›āļāļīāļšāļąāļ•āļīāđ€āļžāļ·āđˆāļ­āļāļģāļˆāļąāļ”āđ‚āļ„āđ‰āļ”āļ‹āđ‰āļģāļ‹āļēāļ

āļŠāļ–āļēāļ›āļąāļ•āļĒāļāļĢāļĢāļĄāļāļēāļĢāļŠāļĄāļąāļ„āļĢāļŠāļĄāļēāļŠāļīāļ iOS StoreKit 2 āđāļĨāļ°āļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļšāđƒāļšāđ€āļŠāļĢāđ‡āļˆ

āļāļēāļĢāļŠāļąāļĄāļ āļēāļĐāļ“āđŒ StoreKit 2: āļāļēāļĢāļˆāļąāļ”āļāļēāļĢāļāļēāļĢāļŠāļĄāļąāļ„āļĢāļŠāļĄāļēāļŠāļīāļāđāļĨāļ°āļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļšāđƒāļšāđ€āļŠāļĢāđ‡āļˆ

āđ€āļŠāļĩāđˆāļĒāļ§āļŠāļēāļāļ„āļģāļ–āļēāļĄāļŠāļąāļĄāļ āļēāļĐāļ“āđŒ iOS āđ€āļāļĩāđˆāļĒāļ§āļāļąāļš StoreKit 2 āļāļēāļĢāļˆāļąāļ”āļāļēāļĢāļāļēāļĢāļŠāļĄāļąāļ„āļĢāļŠāļĄāļēāļŠāļīāļ āļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļšāđƒāļšāđ€āļŠāļĢāđ‡āļˆ āđāļĨāļ°āļāļēāļĢāļ™āļģāļāļēāļĢāļ‹āļ·āđ‰āļ­āđƒāļ™āđāļ­āļ›āđ„āļ›āđƒāļŠāđ‰ āļžāļĢāđ‰āļ­āļĄāļ•āļąāļ§āļ­āļĒāđˆāļēāļ‡āđ‚āļ„āđ‰āļ” Swift āļ—āļĩāđˆāđƒāļŠāđ‰āļ‡āļēāļ™āđ„āļ”āđ‰āļˆāļĢāļīāļ‡