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