Files
skills/ios-application-dev/references/accessibility.md
shihao 6487becf60 Initial commit: add all skills files
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 16:52:49 +08:00

6.7 KiB

Accessibility

iOS accessibility guide covering Dynamic Type, semantic colors, VoiceOver, and motion adaptation.

Dynamic Type

Using System Fonts

private func setupLabels() {
    let titleLabel = UILabel()
    titleLabel.font = .preferredFont(forTextStyle: .headline)
    titleLabel.adjustsFontForContentSizeCategory = true
    
    let bodyLabel = UILabel()
    bodyLabel.font = .preferredFont(forTextStyle: .body)
    bodyLabel.adjustsFontForContentSizeCategory = true
    bodyLabel.numberOfLines = 0
}

Custom Font Scaling

extension UIFont {
    static func scaled(_ name: String, size: CGFloat, for style: TextStyle) -> UIFont {
        guard let font = UIFont(name: name, size: size) else {
            return .preferredFont(forTextStyle: style)
        }
        return UIFontMetrics(forTextStyle: style).scaledFont(for: font)
    }
}

let customFont = UIFont.scaled("Avenir-Medium", size: 16, for: .body)

Text Style Reference

Style Default Size Usage
.largeTitle 34pt Screen titles
.title1 28pt Primary headings
.title2 22pt Secondary headings
.title3 20pt Tertiary headings
.headline 17pt (semibold) Important information
.body 17pt Body text
.callout 16pt Explanatory text
.subheadline 15pt Subtitles
.footnote 13pt Footnotes
.caption1 12pt Labels
.caption2 11pt Small labels

Adapting Layout for Large Text

override func traitCollectionDidChange(_ previous: UITraitCollection?) {
    super.traitCollectionDidChange(previous)
    
    let isLargeText = traitCollection.preferredContentSizeCategory.isAccessibilityCategory
    contentStack.axis = isLargeText ? .vertical : .horizontal
    
    if isLargeText {
        iconImageView.snp.remakeConstraints { make in
            make.size.equalTo(64)
        }
    } else {
        iconImageView.snp.remakeConstraints { make in
            make.size.equalTo(44)
        }
    }
}

Semantic Colors

Use system semantic colors for automatic Dark Mode adaptation:

view.backgroundColor = .systemBackground
containerView.backgroundColor = .secondarySystemBackground
cardView.backgroundColor = .tertiarySystemBackground

titleLabel.textColor = .label
subtitleLabel.textColor = .secondaryLabel
hintLabel.textColor = .tertiaryLabel
placeholderLabel.textColor = .placeholderText

separatorView.backgroundColor = .separator
borderView.layer.borderColor = UIColor.separator.cgColor

System Color Reference

Color Light Mode Dark Mode Usage
.systemBackground White Black Main background
.secondarySystemBackground Light gray Dark gray Card/grouped background
.tertiarySystemBackground Lighter gray Medium gray Nested content background
.label Black White Primary text
.secondaryLabel Gray Light gray Secondary text
.tertiaryLabel Light gray Dark gray Auxiliary text

Custom Color Adaptation

extension UIColor {
    static let customAccent = UIColor { traitCollection in
        switch traitCollection.userInterfaceStyle {
        case .dark:
            return UIColor(red: 0.4, green: 0.8, blue: 1.0, alpha: 1.0)
        default:
            return UIColor(red: 0.0, green: 0.5, blue: 0.8, alpha: 1.0)
        }
    }
}

VoiceOver

Basic Labels

let cartButton = UIButton(type: .system)
cartButton.setImage(UIImage(systemName: "cart.badge.plus"), for: .normal)
cartButton.accessibilityLabel = "Add to cart"

let ratingView = UIView()
ratingView.accessibilityLabel = "Rating: 4 out of 5 stars"

let closeButton = UIButton()
closeButton.accessibilityLabel = "Close"
closeButton.accessibilityHint = "Dismisses this dialog"

Custom Accessibility

class ProductCell: UICollectionViewCell {
    override var accessibilityLabel: String? {
        get {
            return "\(product.name), \(product.price), \(product.isAvailable ? "In stock" : "Out of stock")"
        }
        set {}
    }
    
    override var accessibilityTraits: UIAccessibilityTraits {
        get {
            var traits: UIAccessibilityTraits = .button
            if product.isSelected {
                traits.insert(.selected)
            }
            return traits
        }
        set {}
    }
}

Accessibility Container

class CustomContainerView: UIView {
    override var isAccessibilityElement: Bool {
        get { false }
        set {}
    }
    
    override var accessibilityElements: [Any]? {
        get {
            return [titleLabel, actionButton, detailLabel]
        }
        set {}
    }
}

VoiceOver Notifications

func didLoadContent() {
    UIAccessibility.post(notification: .screenChanged, argument: headerLabel)
}

func didUpdateStatus() {
    UIAccessibility.post(notification: .announcement, argument: "Download complete")
}

Reduce Motion

func animateTransition() {
    let duration: TimeInterval = UIAccessibility.isReduceMotionEnabled ? 0 : 0.3
    UIView.animate(withDuration: duration) {
        self.cardView.alpha = 1
    }
}

func showPopup() {
    if UIAccessibility.isReduceMotionEnabled {
        popupView.alpha = 1
    } else {
        popupView.transform = CGAffineTransform(scaleX: 0.8, y: 0.8)
        popupView.alpha = 0
        UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0) {
            self.popupView.transform = .identity
            self.popupView.alpha = 1
        }
    }
}

Observing Setting Changes

NotificationCenter.default.addObserver(
    self,
    selector: #selector(reduceMotionChanged),
    name: UIAccessibility.reduceMotionStatusDidChangeNotification,
    object: nil
)

@objc func reduceMotionChanged() {
    updateAnimationSettings()
}

Accessibility Checklist

Basic Requirements

  • All icon buttons have accessibilityLabel
  • Custom controls have correct accessibilityTraits
  • Images have accessibilityLabel or marked as decorative
  • Forms have clear error messages

Dynamic Type

  • Using preferredFont(forTextStyle:)
  • Set adjustsFontForContentSizeCategory = true
  • Layout adapts at accessibility sizes
  • Text is not truncated

Color Contrast

  • Body text contrast >= 4.5:1
  • Large text contrast >= 3:1
  • Information not conveyed by color alone

Motion

  • Respect Reduce Motion setting
  • No flashing or rapid animation
  • Auto-playing animations can be paused

Interaction

  • Touch targets >= 44x44pt
  • Gestures have alternative actions
  • Timeouts can be extended

UIKit, VoiceOver, Dynamic Type, and Apple are trademarks of Apple Inc.