Initial commit: add all skills files
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
259
ios-application-dev/references/accessibility.md
Normal file
259
ios-application-dev/references/accessibility.md
Normal file
@@ -0,0 +1,259 @@
|
||||
# Accessibility
|
||||
|
||||
iOS accessibility guide covering Dynamic Type, semantic colors, VoiceOver, and motion adaptation.
|
||||
|
||||
## Dynamic Type
|
||||
|
||||
### Using System Fonts
|
||||
|
||||
```swift
|
||||
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
|
||||
|
||||
```swift
|
||||
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
|
||||
|
||||
```swift
|
||||
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:
|
||||
|
||||
```swift
|
||||
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
|
||||
|
||||
```swift
|
||||
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
|
||||
|
||||
```swift
|
||||
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
|
||||
|
||||
```swift
|
||||
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
|
||||
|
||||
```swift
|
||||
class CustomContainerView: UIView {
|
||||
override var isAccessibilityElement: Bool {
|
||||
get { false }
|
||||
set {}
|
||||
}
|
||||
|
||||
override var accessibilityElements: [Any]? {
|
||||
get {
|
||||
return [titleLabel, actionButton, detailLabel]
|
||||
}
|
||||
set {}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### VoiceOver Notifications
|
||||
|
||||
```swift
|
||||
func didLoadContent() {
|
||||
UIAccessibility.post(notification: .screenChanged, argument: headerLabel)
|
||||
}
|
||||
|
||||
func didUpdateStatus() {
|
||||
UIAccessibility.post(notification: .announcement, argument: "Download complete")
|
||||
}
|
||||
```
|
||||
|
||||
## Reduce Motion
|
||||
|
||||
```swift
|
||||
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
|
||||
|
||||
```swift
|
||||
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.*
|
||||
Reference in New Issue
Block a user