Initial commit: add all skills files

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-10 16:52:49 +08:00
commit 6487becf60
396 changed files with 108871 additions and 0 deletions

View File

@@ -0,0 +1,178 @@
---
name: ios-application-dev
description: |
iOS application development guide covering UIKit, SnapKit, and SwiftUI. Includes touch targets, safe areas, navigation patterns, Dynamic Type, Dark Mode, accessibility, collection views, common UI components, and SwiftUI design guidelines. For detailed references on specific topics, see the reference files.
Use when: developing iOS apps, implementing UI, reviewing iOS code, working with UIKit/SnapKit/SwiftUI layouts, building iPhone interfaces, Swift mobile development, Apple HIG compliance, iOS accessibility implementation.
license: MIT
metadata:
author: MiniMax-OpenSource
version: "1.0.0"
category: mobile
sources:
- Apple Human Interface Guidelines
- Apple Developer Documentation
---
# iOS Application Development Guide
A practical guide for building iOS applications using UIKit, SnapKit, and SwiftUI. Focuses on proven patterns and Apple platform conventions.
## Quick Reference
### UIKit
| Purpose | Component |
|---------|-----------|
| Main sections | `UITabBarController` |
| Drill-down | `UINavigationController` |
| Focused task | Sheet presentation |
| Critical choice | `UIAlertController` |
| Secondary actions | `UIContextMenuInteraction` |
| List content | `UICollectionView` + `DiffableDataSource` |
| Sectioned list | `DiffableDataSource` + `headerMode` |
| Grid layout | `UICollectionViewCompositionalLayout` |
| Search | `UISearchController` |
| Share | `UIActivityViewController` |
| Location (once) | `CLLocationButton` |
| Feedback | `UIImpactFeedbackGenerator` |
| Linear layout | `UIStackView` |
| Custom shapes | `CAShapeLayer` + `UIBezierPath` |
| Gradients | `CAGradientLayer` |
| Modern buttons | `UIButton.Configuration` |
| Dynamic text | `UIFontMetrics` + `preferredFont` |
| Dark mode | Semantic colors (`.systemBackground`, `.label`) |
| Permissions | Contextual request + `AVCaptureDevice` |
| Lifecycle | `UIApplication` notifications |
### SwiftUI
| Purpose | Component |
|---------|-----------|
| Main sections | `TabView` + `tabItem` |
| Drill-down | `NavigationStack` + `NavigationPath` |
| Focused task | `.sheet` + `presentationDetents` |
| Critical choice | `.alert` |
| Secondary actions | `.contextMenu` |
| List content | `List` + `.insetGrouped` |
| Search | `.searchable` |
| Share | `ShareLink` |
| Location (once) | `LocationButton` |
| Feedback | `UIImpactFeedbackGenerator` |
| Progress (known) | `ProgressView(value:total:)` |
| Progress (unknown) | `ProgressView()` |
| Dynamic text | `.font(.body)` semantic styles |
| Dark mode | `.primary`, `.secondary`, `Color(.systemBackground)` |
| Scene lifecycle | `@Environment(\.scenePhase)` |
| Reduce motion | `@Environment(\.accessibilityReduceMotion)` |
| Dynamic type | `@Environment(\.dynamicTypeSize)` |
## Core Principles
### Layout
- Touch targets >= 44pt
- Content within safe areas (SwiftUI respects by default, use `.ignoresSafeArea()` only for backgrounds)
- Use 8pt spacing increments (8, 16, 24, 32, 40, 48)
- Primary actions in thumb zone
- Support all screen sizes (iPhone SE 375pt to Pro Max 430pt)
### Typography
- UIKit: `preferredFont(forTextStyle:)` + `adjustsFontForContentSizeCategory = true`
- SwiftUI: semantic text styles `.headline`, `.body`, `.caption`
- Custom fonts: `UIFontMetrics` / `Font.custom(_:size:relativeTo:)`
- Adapt layout at accessibility sizes (minimum 11pt)
### Colors
- Use semantic system colors (`.systemBackground`, `.label`, `.primary`, `.secondary`)
- Asset catalog variants for custom colors (Any/Dark Appearance)
- No color-only information (pair with icons or text)
- Contrast ratio >= 4.5:1 for normal text, 3:1 for large text
### Accessibility
- Labels on icon buttons (`.accessibilityLabel()`)
- Reduce motion respected (`@Environment(\.accessibilityReduceMotion)`)
- Logical reading order (`.accessibilitySortPriority()`)
- Support Bold Text, Increase Contrast preferences
### Navigation
- Tab bar (3-5 sections) stays visible during navigation
- Back swipe works (never override system gestures)
- State preserved across tabs (`@SceneStorage`, `@State`)
- Never use hamburger menus
### Privacy & Permissions
- Request permissions in context (not at launch)
- Custom explanation before system dialog
- Support Sign in with Apple
- Respect ATT denial
## Checklist
### Layout
- [ ] Touch targets >= 44pt
- [ ] Content within safe areas
- [ ] Primary actions in thumb zone (bottom half)
- [ ] Flexible widths for all screen sizes (SE to Pro Max)
- [ ] Spacing aligns to 8pt grid
### Typography
- [ ] Semantic text styles or UIFontMetrics-scaled custom fonts
- [ ] Dynamic Type supported up to accessibility sizes
- [ ] Layouts reflow at large sizes (no truncation)
- [ ] Minimum text size 11pt
### Colors
- [ ] Semantic system colors or light/dark asset variants
- [ ] Dark Mode is intentional (not just inverted)
- [ ] No color-only information
- [ ] Text contrast >= 4.5:1 (normal) / 3:1 (large)
- [ ] Single accent color for interactive elements
### Accessibility
- [ ] VoiceOver labels on all interactive elements
- [ ] Logical reading order
- [ ] Bold Text preference respected
- [ ] Reduce Motion disables decorative animations
- [ ] All gestures have alternative access paths
### Navigation
- [ ] Tab bar for 3-5 top-level sections
- [ ] No hamburger/drawer menus
- [ ] Tab bar stays visible during navigation
- [ ] Back swipe works throughout
- [ ] State preserved across tabs
### Components
- [ ] Alerts for critical decisions only
- [ ] Sheets have dismiss path (button and/or swipe)
- [ ] List rows >= 44pt tall
- [ ] Destructive buttons use `.destructive` role
### Privacy
- [ ] Permissions requested in context (not at launch)
- [ ] Custom explanation before system permission dialog
- [ ] Sign in with Apple offered with other providers
- [ ] Basic features usable without account
- [ ] ATT prompt shown if tracking, denial respected
### System Integration
- [ ] App handles interruptions gracefully (calls, background, Siri)
- [ ] App content indexed for Spotlight
- [ ] Share Sheet available for shareable content
## References
| Topic | Reference |
|-------|-----------|
| Touch Targets, Safe Area, CollectionView | [Layout System](references/layout-system.md) |
| TabBar, NavigationController, Modal | [Navigation Patterns](references/navigation-patterns.md) |
| StackView, Button, Alert, Search, ContextMenu | [UIKit Components](references/uikit-components.md) |
| CAShapeLayer, CAGradientLayer, Core Animation | [Graphics & Animation](references/graphics-animation.md) |
| Dynamic Type, Semantic Colors, VoiceOver | [Accessibility](references/accessibility.md) |
| Permissions, Location, Share, Lifecycle, Haptics | [System Integration](references/system-integration.md) |
| Metal Shaders & GPU | [Metal Shader Reference](references/metal-shader.md) |
| SwiftUI HIG, Components, Patterns, Anti-Patterns | [SwiftUI Design Guidelines](references/swiftui-design-guidelines.md) |
| Optionals, Protocols, async/await, ARC, Error Handling | [Swift Coding Standards](references/swift-coding-standards.md) |
---
Swift, SwiftUI, UIKit, SF Symbols, Metal, and Apple are trademarks of Apple Inc. SnapKit is a trademark of its respective owners.

View 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.*

View File

@@ -0,0 +1,350 @@
# Graphics & Animation
iOS graphics and animation guide covering CAShapeLayer, CAGradientLayer, UIBezierPath, and Core Animation.
## CAShapeLayer
For custom shapes, paths, and animations:
```swift
class CircularProgressView: UIView {
private let trackLayer = CAShapeLayer()
private let progressLayer = CAShapeLayer()
var progress: CGFloat = 0 {
didSet { updateProgress() }
}
override init(frame: CGRect) {
super.init(frame: frame)
setupLayers()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupLayers()
}
private func setupLayers() {
let center = CGPoint(x: bounds.midX, y: bounds.midY)
let radius = min(bounds.width, bounds.height) / 2 - 10
let startAngle = -CGFloat.pi / 2
let endAngle = startAngle + 2 * CGFloat.pi
let circularPath = UIBezierPath(
arcCenter: center,
radius: radius,
startAngle: startAngle,
endAngle: endAngle,
clockwise: true
)
trackLayer.path = circularPath.cgPath
trackLayer.strokeColor = UIColor.systemGray5.cgColor
trackLayer.fillColor = UIColor.clear.cgColor
trackLayer.lineWidth = 10
trackLayer.lineCap = .round
layer.addSublayer(trackLayer)
progressLayer.path = circularPath.cgPath
progressLayer.strokeColor = UIColor.systemBlue.cgColor
progressLayer.fillColor = UIColor.clear.cgColor
progressLayer.lineWidth = 10
progressLayer.lineCap = .round
progressLayer.strokeEnd = 0
layer.addSublayer(progressLayer)
}
override func layoutSubviews() {
super.layoutSubviews()
setupLayers()
}
private func updateProgress() {
progressLayer.strokeEnd = progress
}
func animateProgress(to value: CGFloat, duration: TimeInterval = 0.5) {
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.fromValue = progressLayer.strokeEnd
animation.toValue = value
animation.duration = duration
animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
progressLayer.strokeEnd = value
progressLayer.add(animation, forKey: "progressAnimation")
}
}
```
## UIBezierPath
### Common Shapes
```swift
let roundedRect = UIBezierPath(
roundedRect: bounds,
cornerRadius: 12
)
let customCorners = UIBezierPath(
roundedRect: bounds,
byRoundingCorners: [.topLeft, .topRight],
cornerRadii: CGSize(width: 16, height: 16)
)
let triangle = UIBezierPath()
triangle.move(to: CGPoint(x: bounds.midX, y: 0))
triangle.addLine(to: CGPoint(x: bounds.maxX, y: bounds.maxY))
triangle.addLine(to: CGPoint(x: 0, y: bounds.maxY))
triangle.close()
let circle = UIBezierPath(
arcCenter: CGPoint(x: bounds.midX, y: bounds.midY),
radius: bounds.width / 2,
startAngle: 0,
endAngle: .pi * 2,
clockwise: true
)
```
### Custom Paths
```swift
let customPath = UIBezierPath()
customPath.move(to: CGPoint(x: 0, y: bounds.height))
customPath.addCurve(
to: CGPoint(x: bounds.width, y: 0),
controlPoint1: CGPoint(x: bounds.width * 0.3, y: bounds.height),
controlPoint2: CGPoint(x: bounds.width * 0.7, y: 0)
)
```
## CAGradientLayer
### Linear Gradient Button
```swift
class GradientButton: UIButton {
private let gradientLayer = CAGradientLayer()
override init(frame: CGRect) {
super.init(frame: frame)
setupGradient()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupGradient()
}
private func setupGradient() {
gradientLayer.colors = [
UIColor.systemBlue.cgColor,
UIColor.systemPurple.cgColor
]
gradientLayer.startPoint = CGPoint(x: 0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1, y: 0.5)
gradientLayer.cornerRadius = 12
layer.insertSublayer(gradientLayer, at: 0)
}
override func layoutSubviews() {
super.layoutSubviews()
gradientLayer.frame = bounds
}
}
```
### Gradient Background View
```swift
class GradientBackgroundView: UIView {
private let gradientLayer = CAGradientLayer()
override init(frame: CGRect) {
super.init(frame: frame)
setupGradient()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupGradient()
}
private func setupGradient() {
gradientLayer.colors = [
UIColor.systemBackground.cgColor,
UIColor.secondarySystemBackground.cgColor
]
gradientLayer.locations = [0.0, 1.0]
gradientLayer.startPoint = CGPoint(x: 0.5, y: 0)
gradientLayer.endPoint = CGPoint(x: 0.5, y: 1)
layer.insertSublayer(gradientLayer, at: 0)
}
override func layoutSubviews() {
super.layoutSubviews()
gradientLayer.frame = bounds
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
gradientLayer.colors = [
UIColor.systemBackground.cgColor,
UIColor.secondarySystemBackground.cgColor
]
}
}
```
### Gradient Types
| Type | Configuration |
|------|---------------|
| Linear (horizontal) | `startPoint: (0, 0.5)`, `endPoint: (1, 0.5)` |
| Linear (vertical) | `startPoint: (0.5, 0)`, `endPoint: (0.5, 1)` |
| Diagonal | `startPoint: (0, 0)`, `endPoint: (1, 1)` |
| Radial | Use `CAGradientLayer.type = .radial` |
## Core Animation
### Basic Animation
```swift
func animateScale() {
let animation = CABasicAnimation(keyPath: "transform.scale")
animation.fromValue = 1.0
animation.toValue = 1.2
animation.duration = 0.3
animation.autoreverses = true
animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
layer.add(animation, forKey: "scaleAnimation")
}
func animatePosition() {
let animation = CABasicAnimation(keyPath: "position")
animation.fromValue = layer.position
animation.toValue = CGPoint(x: 200, y: 200)
animation.duration = 0.5
layer.add(animation, forKey: "positionAnimation")
}
```
### Keyframe Animation
```swift
func animateAlongPath() {
let path = UIBezierPath()
path.move(to: CGPoint(x: 50, y: 50))
path.addCurve(
to: CGPoint(x: 250, y: 250),
controlPoint1: CGPoint(x: 150, y: 50),
controlPoint2: CGPoint(x: 50, y: 250)
)
let animation = CAKeyframeAnimation(keyPath: "position")
animation.path = path.cgPath
animation.duration = 2.0
animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
layer.add(animation, forKey: "pathAnimation")
}
```
### Animation Group
```swift
func animateMultiple() {
let scaleAnimation = CABasicAnimation(keyPath: "transform.scale")
scaleAnimation.fromValue = 1.0
scaleAnimation.toValue = 1.5
let opacityAnimation = CABasicAnimation(keyPath: "opacity")
opacityAnimation.fromValue = 1.0
opacityAnimation.toValue = 0.0
let group = CAAnimationGroup()
group.animations = [scaleAnimation, opacityAnimation]
group.duration = 0.5
group.fillMode = .forwards
group.isRemovedOnCompletion = false
layer.add(group, forKey: "multipleAnimations")
}
```
### Spring Animation
```swift
func springAnimation() {
let spring = CASpringAnimation(keyPath: "transform.scale")
spring.fromValue = 0.8
spring.toValue = 1.0
spring.damping = 10
spring.stiffness = 100
spring.mass = 1
spring.initialVelocity = 5
spring.duration = spring.settlingDuration
layer.add(spring, forKey: "springAnimation")
}
```
## UIView Animation
### Basic UIView Animation
```swift
UIView.animate(withDuration: 0.3) {
self.view.alpha = 1.0
self.view.transform = .identity
}
UIView.animate(withDuration: 0.3, delay: 0, options: [.curveEaseInOut]) {
self.cardView.frame.origin.y = 100
} completion: { _ in
self.didFinishAnimation()
}
```
### Spring Animation
```swift
UIView.animate(
withDuration: 0.6,
delay: 0,
usingSpringWithDamping: 0.7,
initialSpringVelocity: 0.5,
options: []
) {
self.popupView.transform = .identity
}
```
### Keyframe Animation
```swift
UIView.animateKeyframes(withDuration: 1.0, delay: 0) {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.25) {
self.view.transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
}
UIView.addKeyframe(withRelativeStartTime: 0.25, relativeDuration: 0.25) {
self.view.transform = CGAffineTransform(rotationAngle: .pi / 4)
}
UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.5) {
self.view.transform = .identity
}
}
```
## Timing Functions
| Name | Description |
|------|-------------|
| `.linear` | Constant speed |
| `.easeIn` | Slow start |
| `.easeOut` | Slow end |
| `.easeInEaseOut` | Slow start and end |
| `.default` | System default |
---
*UIKit, Core Animation, and Apple are trademarks of Apple Inc.*

View File

@@ -0,0 +1,199 @@
# Layout System
iOS layout system guide covering touch targets, safe areas, UICollectionView, and Compositional Layout.
## Touch Targets
Interactive elements need adequate tap areas. The recommended minimum is 44x44 points.
```swift
let actionButton = UIButton(type: .system)
actionButton.setTitle("Submit", for: .normal)
view.addSubview(actionButton)
actionButton.snp.makeConstraints { make in
make.height.greaterThanOrEqualTo(44)
make.leading.trailing.equalToSuperview().inset(16)
make.bottom.equalTo(view.safeAreaLayoutGuide).offset(-16)
}
```
Use 8-point increments for spacing (8, 16, 24, 32, 40, 48) to maintain visual consistency.
## Safe Area
Always constrain content to the safe area to avoid the notch, Dynamic Island, and home indicator.
```swift
class MainViewController: UIViewController {
private let contentStack = UIStackView()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
contentStack.axis = .vertical
contentStack.spacing = 16
view.addSubview(contentStack)
contentStack.snp.makeConstraints { make in
make.top.bottom.equalTo(view.safeAreaLayoutGuide)
make.leading.trailing.equalTo(view.safeAreaLayoutGuide).inset(16)
}
}
}
```
## UICollectionView with Diffable Data Source
```swift
class ItemsViewController: UIViewController {
enum Section { case main }
private var collectionView: UICollectionView!
private var dataSource: UICollectionViewDiffableDataSource<Section, Item>!
override func viewDidLoad() {
super.viewDidLoad()
setupCollectionView()
configureDataSource()
}
private func setupCollectionView() {
var config = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
config.trailingSwipeActionsConfigurationProvider = { [weak self] indexPath in
self?.makeSwipeActions(for: indexPath)
}
let layout = UICollectionViewCompositionalLayout.list(using: config)
collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
view.addSubview(collectionView)
collectionView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}
private func configureDataSource() {
let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, Item> {
cell, indexPath, item in
var content = cell.defaultContentConfiguration()
content.text = item.title
content.secondaryText = item.subtitle
cell.contentConfiguration = content
}
dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) {
collectionView, indexPath, item in
collectionView.dequeueConfiguredReusableCell(
using: cellRegistration, for: indexPath, item: item
)
}
}
func updateItems(_ items: [Item]) {
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
snapshot.appendSections([.main])
snapshot.appendItems(items)
dataSource.apply(snapshot)
}
}
```
## Grid Layout
```swift
private func createGridLayout() -> UICollectionViewLayout {
let itemSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1/3),
heightDimension: .fractionalHeight(1.0)
)
let item = NSCollectionLayoutItem(layoutSize: itemSize)
item.contentInsets = NSDirectionalEdgeInsets(top: 2, leading: 2, bottom: 2, trailing: 2)
let groupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalWidth(1/3)
)
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
return UICollectionViewCompositionalLayout(section: section)
}
```
## Sectioned List with Headers
```swift
class CategorizedListVC: UIViewController {
enum Section: Hashable {
case favorites, recent, all
}
private var dataSource: UICollectionViewDiffableDataSource<Section, Item>!
private func setupCollectionView() {
var config = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
config.headerMode = .supplementary
let layout = UICollectionViewCompositionalLayout.list(using: config)
collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
}
private func configureDataSource() {
let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, Item> {
cell, indexPath, item in
var content = cell.defaultContentConfiguration()
content.text = item.title
cell.contentConfiguration = content
}
let headerRegistration = UICollectionView.SupplementaryRegistration<UICollectionViewListCell>(
elementKind: UICollectionView.elementKindSectionHeader
) { [weak self] header, elementKind, indexPath in
guard let section = self?.dataSource.sectionIdentifier(for: indexPath.section) else { return }
var content = header.defaultContentConfiguration()
content.text = self?.title(for: section)
header.contentConfiguration = content
}
dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) {
collectionView, indexPath, item in
collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: item)
}
dataSource.supplementaryViewProvider = { collectionView, kind, indexPath in
collectionView.dequeueConfiguredReusableSupplementary(using: headerRegistration, for: indexPath)
}
}
func applySnapshot(favorites: [Item], recent: [Item], all: [Item]) {
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
if !favorites.isEmpty {
snapshot.appendSections([.favorites])
snapshot.appendItems(favorites, toSection: .favorites)
}
if !recent.isEmpty {
snapshot.appendSections([.recent])
snapshot.appendItems(recent, toSection: .recent)
}
snapshot.appendSections([.all])
snapshot.appendItems(all, toSection: .all)
dataSource.apply(snapshot)
}
}
```
## Spacing Guidelines
| Spacing | Usage |
|---------|-------|
| 8pt | Compact element spacing |
| 16pt | Standard padding |
| 24pt | Section spacing |
| 32pt | Large section separation |
| 48pt | Screen margins (large screens) |
---
*UIKit and Apple are trademarks of Apple Inc. SnapKit is a trademark of its respective owners.*

View File

@@ -0,0 +1,178 @@
# Metal Shader Reference
Expert reference for Metal shaders, real-time rendering, and Apple's Tile-Based Deferred Rendering (TBDR) architecture.
## Core Principles
**Half precision first → Leverage TBDR → Function constant specialization → Use Intersector API**
### When to Use
- Metal Shading Language (MSL) development
- Apple GPU optimization (TBDR architecture)
- PBR rendering pipelines
- Compute shaders and parallel processing
- Apple Silicon ray tracing
- GPU profiling and debugging
### When NOT to Use
- WebGL/GLSL (different architecture)
- CUDA (NVIDIA only)
- OpenGL (deprecated on Apple)
- CPU-side optimization
## Expert vs Novice
| Topic | Novice | Expert |
|-------|--------|--------|
| Data types | `float` everywhere | Default `half`, `float` only for position/depth |
| Branching | Runtime conditionals | Function constants for compile-time elimination |
| Memory | Everything in device | Know constant/device/threadgroup tradeoffs |
| Architecture | Treat as desktop GPU | Understand TBDR: tile memory is free, bandwidth is expensive |
| Ray tracing | intersection queries | intersector API (hardware-aligned) |
| Debugging | print debugging | GPU capture, shader profiler, occupancy analysis |
## Common Anti-Patterns
| Anti-Pattern | Problem | Solution |
|--------------|---------|----------|
| 32-bit floats | Wastes registers, reduces occupancy, doubles bandwidth | Default `half`, `float` only for position/depth |
| Ignoring TBDR | Not using free tile memory | Use `[[color(n)]]`, memoryless targets |
| Runtime constant branches | Warp divergence, wastes ALU | Function constants + pipeline specialization |
| intersection queries | Not hardware-aligned | Use intersector API |
## Metal Evolution
| Era | Key Development |
|-----|-----------------|
| Metal 2.x | OpenGL migration, basic compute |
| Apple Silicon | Unified memory, tile shaders critical |
| Metal 3 | Mesh shaders, hardware-accelerated ray tracing |
| Latest | Neural Engine + GPU cooperation, Vision Pro foveated rendering |
**Apple Family 9 Note**: Threadgroup memory less advantageous vs direct device access.
## Shader Types
| Type | Purpose | Key Attributes |
|------|---------|----------------|
| Vertex | Vertex transformation | `[[stage_in]]`, `[[buffer(n)]]` |
| Fragment | Pixel shading | `[[color(n)]]`, `[[texture(n)]]` |
| Compute/Kernel | General computation | `[[thread_position_in_grid]]` |
| Tile | TBDR-specific | `[[imageblock]]` |
| Mesh | Metal 3 geometry | `[[mesh_id]]` |
## Rendering Techniques
| Technique | Description |
|-----------|-------------|
| Fullscreen quad | 4 vertex triangle strip, no MVP, post-processing basis |
| PBR Cook-Torrance | Fresnel Schlick + GGX Distribution + Smith Geometry |
| Blinn-Phong | Simple specular, half-vector calculation |
## Procedural Generation
| Technique | Use Case |
|-----------|----------|
| Hash functions | Pseudo-random basis for noise, random sampling |
| Voronoi | Cell textures, stones, cracks |
| Value/Perlin Noise | Continuous random fields |
| FBM | Multi-octave layering, fractal terrain, clouds |
| Domain Warping | Coordinate distortion, organic shapes |
## Numerical Techniques
| Technique | Formula |
|-----------|---------|
| Central difference gradient | `(f(x+h) - f(x-h)) / (2h)` |
| Smoothstep | `x * x * (3 - 2 * x)` |
| SDF operations | `min/max/smooth_min` boolean ops |
## SwiftUI + MTKView Integration
### Architecture Pattern
```
MetalView (UIViewRepresentable)
└── Coordinator = Renderer (MTKViewDelegate)
├── MTLDevice
├── MTLCommandQueue
├── MTLRenderPipelineState
└── MTLBuffer (vertices, uniforms)
```
### Uniform Alignment Rules
| Swift Type | Metal Type | Alignment |
|------------|------------|-----------|
| `Float` | `float` | 4 bytes |
| `SIMD2<Float>` | `float2` | 8 bytes |
| `SIMD3<Float>` | `float3` | **16 bytes** |
| `SIMD4<Float>` | `float4` | 16 bytes |
**Key**: `float3` aligns to 16 bytes. Use `MemoryLayout<T>.size` to verify.
## Command Line Tools
| Command | Purpose |
|---------|---------|
| `xcrun metal -c shader.metal -o shader.air` | Compile to AIR |
| `xcrun metallib shader.air -o shader.metallib` | Link to metallib |
| `xcrun metal shader.metal -o shader.metallib` | One-step compile & link |
| `xcrun metal -Weverything -c shader.metal` | Syntax check |
| `xcrun metal-objdump --disassemble shader.metallib` | Disassemble |
## GPU Debugging
### Xcode Workflow
1. **GPU Capture**: ⌘⇧⌥G
2. **Shader Profiler**: Select draw call → View Shader
3. **Memory Viewer**: Inspect buffer/texture
4. **Performance HUD**: Enable in device options
### Key Metrics
| Metric | Healthy Value | Low Value Cause |
|--------|---------------|-----------------|
| GPU Occupancy | > 80% | Memory bandwidth bottleneck |
| ALU Utilization | > 60% | Waiting on memory |
| Bandwidth | As low as possible | TBDR should minimize store |
### Debug Utility Functions
| Function | Purpose |
|----------|---------|
| heatmap | Value visualization (blue→green→red) |
| debugNaN | NaN/Inf detection (magenta marker) |
| visualizeDepth | Linearized depth visualization |
## Performance Optimization Checklist
### Data Types
- [ ] Default `half`, `float` only for position/depth
### Memory Management
- [ ] Constants in constant address space
- [ ] Use `.storageModeShared`
- [ ] Leverage tile memory (TBDR free reads)
- [ ] Avoid unnecessary render target stores
### Branch Optimization
- [ ] Function constants to eliminate branches
- [ ] Fixed loop bounds (GPU unrolling)
### Rendering Tips
- [ ] Fullscreen quad with 4 vertex triangle strip
- [ ] Procedural textures to avoid sampling bandwidth
- [ ] `[[early_fragment_tests]]` for early depth test
- [ ] `setFragmentBytes` for small data
### Compute Optimization
- [ ] Vectorize (SIMD)
- [ ] Reduce register pressure
---
*Metal, Apple Silicon, and Xcode are trademarks of Apple Inc.*

View File

@@ -0,0 +1,175 @@
# Navigation Patterns
iOS navigation patterns guide covering Tab navigation, Navigation Controller, and modal presentation.
## Tab-Based Navigation
For apps with 3-5 main sections:
```swift
class AppTabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
let homeNav = UINavigationController(rootViewController: HomeVC())
homeNav.tabBarItem = UITabBarItem(
title: "Home",
image: UIImage(systemName: "house"),
selectedImage: UIImage(systemName: "house.fill")
)
let searchNav = UINavigationController(rootViewController: SearchVC())
searchNav.tabBarItem = UITabBarItem(
title: "Search",
image: UIImage(systemName: "magnifyingglass"),
tag: 1
)
let profileNav = UINavigationController(rootViewController: ProfileVC())
profileNav.tabBarItem = UITabBarItem(
title: "Profile",
image: UIImage(systemName: "person"),
selectedImage: UIImage(systemName: "person.fill")
)
viewControllers = [homeNav, searchNav, profileNav]
}
}
```
### Tab Bar Best Practices
| Principle | Description |
|-----------|-------------|
| Limit count | Maximum 5 tabs, use More for additional |
| Always visible | Tab bar stays visible at all navigation levels |
| State preservation | Preserve navigation state when switching tabs |
| Icon choice | Use SF Symbols, provide selected/unselected states |
## Navigation Controller
Use large titles for root views:
```swift
class ListViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
title = "Items"
navigationController?.navigationBar.prefersLargeTitles = true
navigationItem.largeTitleDisplayMode = .always
}
func pushDetail(_ item: Item) {
let detail = DetailViewController(item: item)
detail.navigationItem.largeTitleDisplayMode = .never
navigationController?.pushViewController(detail, animated: true)
}
}
```
### Navigation Bar Configuration
```swift
class CustomNavigationController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
let appearance = UINavigationBarAppearance()
appearance.configureWithDefaultBackground()
navigationBar.standardAppearance = appearance
navigationBar.scrollEdgeAppearance = appearance
navigationBar.compactAppearance = appearance
}
}
```
### Navigation Bar Buttons
```swift
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.rightBarButtonItem = UIBarButtonItem(
image: UIImage(systemName: "plus"),
style: .plain,
target: self,
action: #selector(addItem)
)
navigationItem.rightBarButtonItems = [
UIBarButtonItem(systemItem: .add, primaryAction: UIAction { _ in }),
UIBarButtonItem(systemItem: .edit, primaryAction: UIAction { _ in })
]
}
```
## Modal Presentation
### Sheet Presentation
```swift
func presentEditor() {
let editorVC = EditorViewController()
let nav = UINavigationController(rootViewController: editorVC)
editorVC.navigationItem.leftBarButtonItem = UIBarButtonItem(
systemItem: .cancel, target: self, action: #selector(dismissEditor)
)
editorVC.navigationItem.rightBarButtonItem = UIBarButtonItem(
systemItem: .done, target: self, action: #selector(saveAndDismiss)
)
if let sheet = nav.sheetPresentationController {
sheet.detents = [.medium(), .large()]
sheet.prefersGrabberVisible = true
sheet.prefersScrollingExpandsWhenScrolledToEdge = false
}
present(nav, animated: true)
}
```
### Custom Detent (iOS 16+)
```swift
if let sheet = nav.sheetPresentationController {
let customDetent = UISheetPresentationController.Detent.custom { context in
return context.maximumDetentValue * 0.4
}
sheet.detents = [customDetent, .large()]
}
```
### Full Screen Presentation
```swift
func presentFullScreen() {
let vc = FullScreenViewController()
vc.modalPresentationStyle = .fullScreen
vc.modalTransitionStyle = .coverVertical
present(vc, animated: true)
}
```
## Presentation Styles
| Style | Usage |
|-------|-------|
| `.automatic` | System default (usually sheet) |
| `.pageSheet` | Card-style, parent view visible |
| `.fullScreen` | Full screen cover |
| `.overFullScreen` | Full screen with transparent background |
| `.popover` | iPad popover |
## Navigation Best Practices
1. **Back gesture** - Ensure edge swipe back always works
2. **State restoration** - Use `UIStateRestoring` to save navigation stack
3. **Depth limit** - Avoid more than 4-5 navigation levels
4. **Cancel button** - Modal views must provide a cancel option
5. **Save confirmation** - Show confirmation dialog for unsaved changes
---
*UIKit, SF Symbols, and Apple are trademarks of Apple Inc.*

View File

@@ -0,0 +1,741 @@
# Swift Coding Standards
Best practices for writing clean, safe, and idiomatic Swift code following Apple's guidelines and modern Swift conventions.
---
## 1. Optionals and Safety
**Impact:** CRITICAL
Swift's optional system eliminates null pointer exceptions through compile-time safety.
### 1.1 Safe Unwrapping with if let
```swift
if let name = optionalName {
print("Hello, \(name)")
}
// Multiple bindings
if let name = userName, let age = userAge, age >= 18 {
print("\(name) is an adult")
}
```
### 1.2 Guard for Early Exit
Use `guard` to exit early when preconditions aren't met:
```swift
func processUser(_ user: User?) {
guard let user = user else { return }
guard !user.name.isEmpty else { return }
print(user.name)
}
```
### 1.3 Nil Coalescing for Defaults
```swift
let displayName = name ?? "Anonymous"
let count = items?.count ?? 0
```
### 1.4 Optional Chaining
```swift
let count = user?.profile?.posts?.count
let uppercased = optionalString?.uppercased()
```
### 1.5 Optional map/flatMap
```swift
let uppercasedName = userName.map { $0.uppercased() }
let userID = userIDString.flatMap { Int($0) }
```
### 1.6 Never Force Unwrap
Avoid `!` force unwrapping. Use safe alternatives:
| Instead of | Use |
|------------|-----|
| `value!` | `if let value = value { }` |
| `array[0]` (unsafe) | `array.first` |
| `dictionary["key"]!` | `dictionary["key", default: defaultValue]` |
---
## 2. Naming Conventions
**Impact:** HIGH
### 2.1 Types: PascalCase
```swift
class UserProfileViewController { }
struct NetworkRequest { }
protocol DataSource { }
enum LoadingState { }
```
### 2.2 Variables and Functions: camelCase
```swift
var userName: String
let maximumRetryCount = 3
func fetchUserProfile() { }
```
### 2.3 Boolean Naming
Use `is`, `has`, `should`, `can` prefixes:
```swift
var isLoading: Bool
var hasCompletedOnboarding: Bool
var shouldShowAlert: Bool
var canEditProfile: Bool
```
### 2.4 Function Naming
Use verb phrases, read like natural English:
```swift
// Good - clear actions
func fetchUsers() async throws -> [User]
func remove(_ item: Item, at index: Int)
func makeIterator() -> Iterator
// Avoid - unclear or redundant
func getUsersData() // "get" is redundant
func doRemove() // vague
```
### 2.5 Parameter Labels
First parameter label can be omitted when obvious:
```swift
func insert(_ element: Element, at index: Int)
func move(from source: Int, to destination: Int)
```
---
## 3. Protocol-Oriented Design
**Impact:** HIGH
Swift favors composition over inheritance through protocols.
### 3.1 Define Capabilities Through Protocols
```swift
protocol DataStore {
func save<T: Codable>(_ item: T, key: String) throws
func load<T: Codable>(key: String) throws -> T?
}
protocol Drawable {
var color: Color { get set }
func draw()
}
```
### 3.2 Protocol Extensions for Default Behavior
```swift
extension Drawable {
func draw() {
print("Drawing with \(color)")
}
}
extension Collection {
func chunked(into size: Int) -> [[Element]] {
stride(from: 0, to: count, by: size).map {
Array(self[$0..<Swift.min($0 + size, count)])
}
}
}
```
### 3.3 Associated Types for Flexibility
```swift
protocol Repository {
associatedtype Item
func fetchAll() async throws -> [Item]
func save(_ item: Item) async throws
}
class UserRepository: Repository {
typealias Item = User
func fetchAll() async throws -> [User] { /* ... */ }
func save(_ item: User) async throws { /* ... */ }
}
```
### 3.4 Protocol Composition
```swift
protocol Named { var name: String { get } }
protocol Aged { var age: Int { get } }
func greet(_ person: Named & Aged) {
print("Hello, \(person.name), age \(person.age)")
}
```
---
## 4. Value Types vs Reference Types
**Impact:** HIGH
### 4.1 Prefer Structs (Value Types)
Use structs for simple data models, independent copies:
```swift
struct User {
var name: String
var email: String
}
struct Point {
var x: Double
var y: Double
}
```
### 4.2 Use Classes When Needed
Use classes for shared mutable state, identity matters:
```swift
class NetworkManager {
static let shared = NetworkManager()
private init() { }
}
class FileHandle {
// Wrapping system resource
}
```
### 4.3 Enums for Finite States
```swift
enum LoadingState {
case idle
case loading
case success(Data)
case failure(Error)
}
enum Result<Success, Failure: Error> {
case success(Success)
case failure(Failure)
}
```
| Type | Use When |
|------|----------|
| `struct` | Data models, coordinates, independent values |
| `class` | Shared state, identity matters, inheritance needed |
| `enum` | Finite set of options, state machines |
---
## 5. Memory Management with ARC
**Impact:** CRITICAL
### 5.1 Breaking Retain Cycles with weak
```swift
class Apartment {
weak var tenant: Person?
}
class Person {
var apartment: Apartment?
}
```
### 5.2 Closure Capture Lists
```swift
// Weak capture for optional self
onComplete = { [weak self] in
self?.processResult()
}
// Capture specific values
let id = user.id
fetchData { [id] result in
print("Fetched for \(id)")
}
```
### 5.3 unowned for Guaranteed Lifetime
Use when reference should never be nil during object lifetime:
```swift
class CreditCard {
unowned let customer: Customer
init(customer: Customer) {
self.customer = customer
}
}
```
| Keyword | Use When |
|---------|----------|
| `weak` | Reference may become nil |
| `unowned` | Reference guaranteed to outlive |
| None | Strong ownership needed |
---
## 6. Error Handling
**Impact:** HIGH
### 6.1 Define Typed Errors
```swift
enum NetworkError: Error {
case invalidURL
case noConnection
case serverError(statusCode: Int)
case decodingFailed(underlying: Error)
}
enum ValidationError: LocalizedError {
case emptyField(name: String)
case invalidFormat(field: String, expected: String)
var errorDescription: String? {
switch self {
case .emptyField(let name):
return "\(name) cannot be empty"
case .invalidFormat(let field, let expected):
return "\(field) must be \(expected)"
}
}
}
```
### 6.2 Throwing Functions
```swift
func fetchUser(id: Int) throws -> User {
guard let url = URL(string: "https://api.example.com/users/\(id)") else {
throw NetworkError.invalidURL
}
// ... implementation
}
```
### 6.3 Do-Catch Handling
```swift
do {
let user = try fetchUser(id: 123)
print(user.name)
} catch NetworkError.serverError(let code) {
print("Server error: \(code)")
} catch NetworkError.noConnection {
print("Check your internet connection")
} catch {
print("Unknown error: \(error)")
}
```
### 6.4 try? and try!
```swift
// try? returns optional (nil on error)
let user = try? fetchUser(id: 123)
// try! crashes on error - use only when failure is programmer error
let config = try! loadBundledConfig()
```
### 6.5 Rethrows
```swift
func perform<T>(_ operation: () throws -> T) rethrows -> T {
return try operation()
}
```
---
## 7. Modern Concurrency (async/await)
**Impact:** CRITICAL
### 7.1 Async Functions
```swift
func fetchUser(id: Int) async throws -> User {
guard let url = URL(string: "https://api.example.com/users/\(id)") else {
throw NetworkError.invalidURL
}
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode(User.self, from: data)
}
// Calling async functions
Task {
do {
let user = try await fetchUser(id: 123)
print(user.name)
} catch {
print("Failed: \(error)")
}
}
```
### 7.2 Parallel Execution with TaskGroup
```swift
func fetchAllUsers(ids: [Int]) async throws -> [User] {
try await withThrowingTaskGroup(of: User.self) { group in
for id in ids {
group.addTask {
try await fetchUser(id: id)
}
}
return try await group.reduce(into: []) { $0.append($1) }
}
}
```
### 7.3 async let for Concurrent Bindings
```swift
async let user = fetchUser(id: 1)
async let posts = fetchPosts(userId: 1)
async let followers = fetchFollowers(userId: 1)
let profile = try await ProfileData(
user: user,
posts: posts,
followers: followers
)
```
### 7.4 Actors for Thread-Safe State
```swift
actor BankAccount {
private var balance: Double = 0
func deposit(_ amount: Double) {
balance += amount
}
func withdraw(_ amount: Double) throws {
guard balance >= amount else {
throw BankError.insufficientFunds
}
balance -= amount
}
func getBalance() -> Double {
balance
}
}
// Usage
let account = BankAccount()
await account.deposit(100)
let balance = await account.getBalance()
```
### 7.5 MainActor for UI Updates
```swift
@MainActor
class ViewModel: ObservableObject {
@Published var isLoading = false
@Published var users: [User] = []
func loadUsers() async {
isLoading = true
defer { isLoading = false }
do {
users = try await fetchUsers()
} catch {
// Handle error
}
}
}
```
### 7.6 Task Cancellation
```swift
func fetchWithTimeout() async throws -> Data {
try await withThrowingTaskGroup(of: Data.self) { group in
group.addTask {
try await fetchData()
}
group.addTask {
try await Task.sleep(for: .seconds(10))
throw TimeoutError()
}
let result = try await group.next()!
group.cancelAll()
return result
}
}
// Check for cancellation
func longOperation() async throws {
for item in items {
try Task.checkCancellation()
await process(item)
}
}
```
---
## 8. Access Control
**Impact:** MEDIUM
### 8.1 Access Levels
| Level | Scope |
|-------|-------|
| `private` | Enclosing declaration only |
| `fileprivate` | Entire source file |
| `internal` | Module (default) |
| `public` | Other modules can access |
| `open` | Other modules can subclass/override |
### 8.2 Best Practices
```swift
public class UserService {
// Public API
public func fetchUser(id: Int) async throws -> User { }
// Internal helper
func buildRequest(for id: Int) -> URLRequest { }
// Private implementation detail
private let session: URLSession
private var cache: [Int: User] = [:]
}
```
### 8.3 Private Setters
```swift
public struct Counter {
public private(set) var count = 0
public mutating func increment() {
count += 1
}
}
```
---
## 9. Generics and Type Constraints
**Impact:** MEDIUM
### 9.1 Generic Functions
```swift
func swapValues<T>(_ a: inout T, _ b: inout T) {
let temp = a
a = b
b = temp
}
```
### 9.2 Type Constraints
```swift
func findIndex<T: Equatable>(of value: T, in array: [T]) -> Int? {
array.firstIndex(of: value)
}
func decode<T: Decodable>(_ type: T.Type, from data: Data) throws -> T {
try JSONDecoder().decode(type, from: data)
}
```
### 9.3 Where Clauses
```swift
func allMatch<C: Collection>(_ collection: C, predicate: (C.Element) -> Bool) -> Bool
where C.Element: Equatable {
collection.allSatisfy(predicate)
}
extension Array where Element: Numeric {
func sum() -> Element {
reduce(0, +)
}
}
```
### 9.4 Opaque Types (some)
```swift
func makeCollection() -> some Collection {
[1, 2, 3]
}
var body: some View {
Text("Hello")
}
```
---
## 10. Property Wrappers
**Impact:** MEDIUM
### 10.1 Common SwiftUI Property Wrappers
| Wrapper | Use Case |
|---------|----------|
| `@State` | View-local mutable state |
| `@Binding` | Two-way connection to parent state |
| `@StateObject` | View-owned observable object |
| `@ObservedObject` | Passed-in observable object |
| `@EnvironmentObject` | Shared object from ancestor |
| `@Environment` | System environment values |
| `@Published` | Observable property in class |
### 10.2 Custom Property Wrappers
```swift
@propertyWrapper
struct Clamped<Value: Comparable> {
private var value: Value
let range: ClosedRange<Value>
var wrappedValue: Value {
get { value }
set { value = min(max(newValue, range.lowerBound), range.upperBound) }
}
init(wrappedValue: Value, _ range: ClosedRange<Value>) {
self.range = range
self.value = min(max(wrappedValue, range.lowerBound), range.upperBound)
}
}
struct Settings {
@Clamped(0...100) var volume: Int = 50
}
```
---
## Quick Reference
### Optionals
```swift
if let x = optional { } // Safe unwrap
guard let x = optional else { return } // Early exit
let x = optional ?? default // Default value
optional?.method() // Optional chaining
optional.map { transform($0) } // Transform if present
```
### Common Patterns
```swift
// Defer for cleanup
func process() {
let file = openFile()
defer { closeFile(file) }
// ... work with file
}
// Lazy initialization
lazy var expensive: ExpensiveObject = {
ExpensiveObject()
}()
// Type inference
let numbers = [1, 2, 3] // [Int]
let doubled = numbers.map { $0 * 2 } // [Int]
```
### Closure Syntax
```swift
// Full syntax
let sorted = names.sorted(by: { (s1: String, s2: String) -> Bool in
return s1 < s2
})
// Shortened
let sorted = names.sorted { $0 < $1 }
// Trailing closure
UIView.animate(withDuration: 0.3) {
view.alpha = 0
}
```
---
## Checklist
### Safety
- [ ] No force unwrapping (`!`) except for IB outlets and known-safe cases
- [ ] All optionals handled with `if let`, `guard let`, or `??`
- [ ] No implicitly unwrapped optionals (`!`) in data models
### Memory
- [ ] Closures use `[weak self]` when capturing self in escaping closures
- [ ] Delegate properties are `weak`
- [ ] No retain cycles between objects
### Concurrency
- [ ] Async functions used instead of completion handlers
- [ ] Actors protect shared mutable state
- [ ] UI updates on `@MainActor`
- [ ] Task cancellation checked in long operations
### Access Control
- [ ] `private` used for implementation details
- [ ] `public` API is minimal and intentional
- [ ] No unnecessary `internal` exposure
### Naming
- [ ] Types use PascalCase
- [ ] Functions and variables use camelCase
- [ ] Booleans have `is`/`has`/`should` prefix
- [ ] Functions read like natural English
---
*Swift and Apple are trademarks of Apple Inc.*

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,401 @@
# System Integration
iOS system integration guide covering permissions, location, sharing, app lifecycle, and haptic feedback.
## Permission Requests
Request permissions contextually, not at launch:
```swift
import AVFoundation
@objc func openCamera() {
AVCaptureDevice.requestAccess(for: .video) { [weak self] granted in
DispatchQueue.main.async {
if granted {
self?.showCameraInterface()
} else {
self?.showPermissionDeniedAlert()
}
}
}
}
```
### Photo Library
```swift
import Photos
func requestPhotoAccess() {
PHPhotoLibrary.requestAuthorization(for: .readWrite) { status in
DispatchQueue.main.async {
switch status {
case .authorized, .limited:
self.showPhotoPicker()
case .denied, .restricted:
self.showSettingsAlert()
default:
break
}
}
}
}
```
### Microphone
```swift
func requestMicrophoneAccess() {
AVAudioSession.sharedInstance().requestRecordPermission { granted in
DispatchQueue.main.async {
if granted {
self.startRecording()
}
}
}
}
```
### Notifications
```swift
import UserNotifications
func requestNotificationPermission() {
UNUserNotificationCenter.current().requestAuthorization(
options: [.alert, .badge, .sound]
) { granted, error in
DispatchQueue.main.async {
if granted {
self.registerForRemoteNotifications()
}
}
}
}
```
## Location Button
For one-time location access without persistent permission:
```swift
import CoreLocationUI
class StoreFinderVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let locationBtn = CLLocationButton()
locationBtn.icon = .arrowFilled
locationBtn.label = .currentLocation
locationBtn.cornerRadius = 20
locationBtn.addTarget(self, action: #selector(findNearby), for: .touchUpInside)
view.addSubview(locationBtn)
locationBtn.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.bottom.equalTo(view.safeAreaLayoutGuide).offset(-24)
}
}
}
```
### Core Location
```swift
import CoreLocation
class LocationManager: NSObject, CLLocationManagerDelegate {
private let manager = CLLocationManager()
func requestLocation() {
manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.requestWhenInUseAuthorization()
}
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
switch manager.authorizationStatus {
case .authorizedWhenInUse, .authorizedAlways:
manager.requestLocation()
case .denied:
showLocationDeniedAlert()
default:
break
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else { return }
handleLocation(location)
}
}
```
## Share Sheet
```swift
@objc func shareContent() {
let items: [Any] = [contentURL, contentImage].compactMap { $0 }
let activityVC = UIActivityViewController(activityItems: items, applicationActivities: nil)
if let popover = activityVC.popoverPresentationController {
popover.sourceView = shareButton
popover.sourceRect = shareButton.bounds
}
present(activityVC, animated: true)
}
```
### Custom Share Items
```swift
class ShareItem: NSObject, UIActivityItemSource {
let title: String
let url: URL
init(title: String, url: URL) {
self.title = title
self.url = url
}
func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
return url
}
func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
return url
}
func activityViewController(_ activityViewController: UIActivityViewController, subjectForActivityType activityType: UIActivity.ActivityType?) -> String {
return title
}
}
```
### Excluding Activities
```swift
let activityVC = UIActivityViewController(activityItems: items, applicationActivities: nil)
activityVC.excludedActivityTypes = [
.addToReadingList,
.assignToContact,
.print
]
```
## App Lifecycle
```swift
class PlayerViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(
self, selector: #selector(onBackground),
name: UIApplication.didEnterBackgroundNotification, object: nil
)
NotificationCenter.default.addObserver(
self, selector: #selector(onForeground),
name: UIApplication.willEnterForegroundNotification, object: nil
)
NotificationCenter.default.addObserver(
self, selector: #selector(onTerminate),
name: UIApplication.willTerminateNotification, object: nil
)
}
@objc private func onBackground() {
saveState()
pausePlayback()
}
@objc private func onForeground() {
restoreState()
resumePlayback()
}
@objc private func onTerminate() {
saveState()
}
}
```
### Scene Lifecycle (iOS 13+)
```swift
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
func sceneDidBecomeActive(_ scene: UIScene) {
// Resume tasks
}
func sceneWillResignActive(_ scene: UIScene) {
// Pause tasks
}
func sceneDidEnterBackground(_ scene: UIScene) {
// Save state
}
func sceneWillEnterForeground(_ scene: UIScene) {
// Prepare UI
}
}
```
### State Preservation
```swift
class ViewController: UIViewController {
override func encodeRestorableState(with coder: NSCoder) {
super.encodeRestorableState(with: coder)
coder.encode(currentItemID, forKey: "currentItemID")
}
override func decodeRestorableState(with coder: NSCoder) {
super.decodeRestorableState(with: coder)
if let itemID = coder.decodeObject(forKey: "currentItemID") as? String {
loadItem(itemID)
}
}
}
```
## Haptic Feedback
```swift
func onTaskComplete() {
UINotificationFeedbackGenerator().notificationOccurred(.success)
}
func onError() {
UINotificationFeedbackGenerator().notificationOccurred(.error)
}
func onWarning() {
UINotificationFeedbackGenerator().notificationOccurred(.warning)
}
func onSelection() {
UISelectionFeedbackGenerator().selectionChanged()
}
func onImpact() {
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
}
```
### Impact Styles
| Style | Usage |
|-------|-------|
| `.light` | Subtle feedback, small UI changes |
| `.medium` | Standard feedback, button presses |
| `.heavy` | Strong feedback, significant actions |
| `.soft` | Gentle feedback, background changes |
| `.rigid` | Sharp feedback, collisions |
### Prepared Feedback
For time-critical haptics, prepare the generator in advance:
```swift
class DraggableView: UIView {
private let impactGenerator = UIImpactFeedbackGenerator(style: .medium)
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
impactGenerator.prepare()
}
func didSnapToPosition() {
impactGenerator.impactOccurred()
}
}
```
## Deep Linking
### URL Schemes
```swift
// In AppDelegate or SceneDelegate
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true) else {
return false
}
switch components.host {
case "item":
if let itemID = components.queryItems?.first(where: { $0.name == "id" })?.value {
navigateToItem(itemID)
return true
}
default:
break
}
return false
}
```
### Universal Links
```swift
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL else {
return false
}
return handleUniversalLink(url)
}
```
## Background Tasks
```swift
import BackgroundTasks
func registerBackgroundTasks() {
BGTaskScheduler.shared.register(
forTaskWithIdentifier: "com.app.refresh",
using: nil
) { task in
self.handleAppRefresh(task: task as! BGAppRefreshTask)
}
}
func scheduleAppRefresh() {
let request = BGAppRefreshTaskRequest(identifier: "com.app.refresh")
request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60)
do {
try BGTaskScheduler.shared.submit(request)
} catch {
print("Could not schedule app refresh: \(error)")
}
}
func handleAppRefresh(task: BGAppRefreshTask) {
scheduleAppRefresh()
let operation = RefreshOperation()
task.expirationHandler = {
operation.cancel()
}
operation.completionBlock = {
task.setTaskCompleted(success: !operation.isCancelled)
}
OperationQueue.main.addOperation(operation)
}
```
---
*UIKit, Core Location, and Apple are trademarks of Apple Inc.*

View File

@@ -0,0 +1,297 @@
# UIKit Components
Common UIKit components guide covering UIStackView, buttons, alerts, search, and context menus.
## UIStackView
Stack views simplify auto layout for linear arrangements:
```swift
class FormViewController: UIViewController {
private let mainStack = UIStackView()
override func viewDidLoad() {
super.viewDidLoad()
mainStack.axis = .vertical
mainStack.spacing = 16
mainStack.alignment = .fill
mainStack.distribution = .fill
view.addSubview(mainStack)
mainStack.snp.makeConstraints { make in
make.top.equalTo(view.safeAreaLayoutGuide).offset(20)
make.leading.trailing.equalToSuperview().inset(16)
}
let headerStack = UIStackView()
headerStack.axis = .horizontal
headerStack.spacing = 12
headerStack.alignment = .center
let avatarView = UIImageView()
avatarView.snp.makeConstraints { make in
make.size.equalTo(48)
}
let labelStack = UIStackView()
labelStack.axis = .vertical
labelStack.spacing = 4
labelStack.addArrangedSubview(titleLabel)
labelStack.addArrangedSubview(subtitleLabel)
headerStack.addArrangedSubview(avatarView)
headerStack.addArrangedSubview(labelStack)
mainStack.addArrangedSubview(headerStack)
mainStack.addArrangedSubview(contentView)
mainStack.addArrangedSubview(actionButton)
mainStack.setCustomSpacing(24, after: headerStack)
}
}
```
### StackView Properties
| Property | Options | Usage |
|----------|---------|-------|
| `axis` | `.horizontal`, `.vertical` | Layout direction |
| `distribution` | `.fill`, `.fillEqually`, `.fillProportionally`, `.equalSpacing`, `.equalCentering` | Space distribution |
| `alignment` | `.fill`, `.leading`, `.center`, `.trailing` | Cross-axis alignment |
| `spacing` | CGFloat | Uniform spacing |
| `setCustomSpacing(_:after:)` | - | Variable spacing |
## UIButton.Configuration (iOS 15+)
```swift
let primaryButton = UIButton(type: .system)
primaryButton.configuration = .filled()
primaryButton.setTitle("Continue", for: .normal)
let secondaryButton = UIButton(type: .system)
secondaryButton.configuration = .tinted()
secondaryButton.setTitle("Save for Later", for: .normal)
let destructiveButton = UIButton(type: .system)
destructiveButton.configuration = .plain()
destructiveButton.setTitle("Remove", for: .normal)
destructiveButton.tintColor = .systemRed
```
### Custom Button Configuration
```swift
var config = UIButton.Configuration.filled()
config.title = "Add to Cart"
config.image = UIImage(systemName: "cart.badge.plus")
config.imagePadding = 8
config.cornerStyle = .capsule
config.baseBackgroundColor = .systemBlue
config.baseForegroundColor = .white
let cartButton = UIButton(configuration: config)
```
### Button State Handling
```swift
var config = UIButton.Configuration.filled()
config.titleTextAttributesTransformer = UIConfigurationTextAttributesTransformer { incoming in
var outgoing = incoming
outgoing.font = .boldSystemFont(ofSize: 16)
return outgoing
}
config.configurationUpdateHandler = { button in
var config = button.configuration
config?.showsActivityIndicator = button.isSelected
button.configuration = config
}
```
## UIAlertController
### Alert
```swift
func confirmDeletion() {
let alert = UIAlertController(
title: "Remove Item?",
message: "This cannot be undone.",
preferredStyle: .alert
)
alert.addAction(UIAlertAction(title: "Remove", style: .destructive) { _ in
self.performDeletion()
})
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
present(alert, animated: true)
}
```
### Action Sheet
```swift
func showOptions() {
let sheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
sheet.addAction(UIAlertAction(title: "Share", style: .default) { _ in })
sheet.addAction(UIAlertAction(title: "Edit", style: .default) { _ in })
sheet.addAction(UIAlertAction(title: "Delete", style: .destructive) { _ in })
sheet.addAction(UIAlertAction(title: "Cancel", style: .cancel))
if let popover = sheet.popoverPresentationController {
popover.sourceView = optionsButton
popover.sourceRect = optionsButton.bounds
}
present(sheet, animated: true)
}
```
### Alert with Text Field
```swift
func showInputAlert() {
let alert = UIAlertController(
title: "Rename",
message: "Enter a new name",
preferredStyle: .alert
)
alert.addTextField { textField in
textField.placeholder = "Name"
textField.autocapitalizationType = .words
}
alert.addAction(UIAlertAction(title: "Save", style: .default) { _ in
if let name = alert.textFields?.first?.text {
self.rename(to: name)
}
})
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
present(alert, animated: true)
}
```
## UISearchController
```swift
class SearchableListVC: UIViewController, UISearchResultsUpdating {
private let searchController = UISearchController(searchResultsController: nil)
private var allItems: [Item] = []
override func viewDidLoad() {
super.viewDidLoad()
setupSearch()
}
private func setupSearch() {
searchController.searchResultsUpdater = self
searchController.obscuresBackgroundDuringPresentation = false
searchController.searchBar.placeholder = "Search"
navigationItem.searchController = searchController
definesPresentationContext = true
}
func updateSearchResults(for searchController: UISearchController) {
let query = searchController.searchBar.text ?? ""
let filtered = query.isEmpty ? allItems : allItems.filter {
$0.title.localizedCaseInsensitiveContains(query)
}
updateItems(filtered)
}
}
```
### Search Bar Configuration
```swift
searchController.searchBar.scopeButtonTitles = ["All", "Recent", "Favorites"]
searchController.searchBar.showsScopeBar = true
searchController.searchBar.delegate = self
extension SearchableListVC: UISearchBarDelegate {
func searchBar(_ searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) {
filterContent(scope: selectedScope)
}
}
```
## UIContextMenuInteraction
```swift
extension PhotoCell: UIContextMenuInteractionDelegate {
func contextMenuInteraction(
_ interaction: UIContextMenuInteraction,
configurationForMenuAtLocation location: CGPoint
) -> UIContextMenuConfiguration? {
UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { _ in
let share = UIAction(
title: "Share",
image: UIImage(systemName: "square.and.arrow.up")
) { _ in }
let favorite = UIAction(
title: "Favorite",
image: UIImage(systemName: "heart")
) { _ in }
let delete = UIAction(
title: "Delete",
image: UIImage(systemName: "trash"),
attributes: .destructive
) { _ in }
return UIMenu(children: [share, favorite, delete])
}
}
}
```
### Context Menu with Preview
```swift
func contextMenuInteraction(
_ interaction: UIContextMenuInteraction,
configurationForMenuAtLocation location: CGPoint
) -> UIContextMenuConfiguration? {
UIContextMenuConfiguration(
identifier: itemID as NSCopying,
previewProvider: { [weak self] in
return self?.makePreviewController()
},
actionProvider: { _ in
return self.makeMenu()
}
)
}
func contextMenuInteraction(
_ interaction: UIContextMenuInteraction,
willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration,
animator: UIContextMenuInteractionCommitAnimating
) {
animator.addCompletion {
self.showDetail()
}
}
```
### CollectionView Context Menu
```swift
func collectionView(
_ collectionView: UICollectionView,
contextMenuConfigurationForItemAt indexPath: IndexPath,
point: CGPoint
) -> UIContextMenuConfiguration? {
let item = dataSource.itemIdentifier(for: indexPath)
return UIContextMenuConfiguration(identifier: indexPath as NSCopying, previewProvider: nil) { _ in
return self.makeMenu(for: item)
}
}
```
---
*UIKit and Apple are trademarks of Apple Inc.*