Appendix A: Production Techniques
From demos to shipping apps: Touch response, gesture handling, motion integration, performance optimization, reusable architecture, and debugging/profiling tools.
Interaction Patterns
Touch Response Best Practices
struct TouchResponsiveShader: ViewModifier {
@State private var touchLocation: CGPoint = .zero
@State private var touchIntensity: Float = 0
@GestureState private var dragLocation: CGPoint? = nil
func body(content: Content) -> some View {
content
.visualEffect { view, proxy in
view.colorEffect(
ShaderLibrary.touchResponse(
.float2(proxy.size),
.float2(Float(touchLocation.x), Float(touchLocation.y)),
.float(touchIntensity)
)
)
}
.gesture(
DragGesture(minimumDistance: 0)
.updating($dragLocation) { value, state, _ in
state = value.location
}
.onChanged { value in
touchLocation = value.location
withAnimation(.easeOut(duration: 0.1)) {
touchIntensity = 1.0
}
}
.onEnded { _ in
withAnimation(.easeOut(duration: 0.5)) {
touchIntensity = 0
}
}
)
}
}
Gesture Handling Patterns
- Immediate Response: < 16ms for touch feedback
- Gesture Prediction: Anticipate gesture completion
- Haptic Integration: Coordinate with shader effects
- Multi-touch: Handle up to 10 simultaneous touches
Motion Integration with CoreMotion
class MotionManager: ObservableObject {
@Published var rotation: SIMD3<Float> = .zero
@Published var acceleration: SIMD3<Float> = .zero
private let motionManager = CMMotionManager()
private let queue = OperationQueue()
func startMotionUpdates() {
guard motionManager.isDeviceMotionAvailable else { return }
motionManager.deviceMotionUpdateInterval = 1.0 / 60.0
motionManager.startDeviceMotionUpdates(to: queue) { [weak self] motion, error in
guard let motion = motion else { return }
DispatchQueue.main.async {
self?.rotation = SIMD3<Float>(
Float(motion.attitude.roll),
Float(motion.attitude.pitch),
Float(motion.attitude.yaw)
)
self?.acceleration = SIMD3<Float>(
Float(motion.userAcceleration.x),
Float(motion.userAcceleration.y),
Float(motion.userAcceleration.z)
)
}
}
}
}
Quality Level Management
enum ShaderQuality: Int {
case low = 0
case medium = 1
case high = 2
case ultra = 3
static func detectOptimal() -> ShaderQuality {
let device = UIDevice.current
if device.supportsProMotion {
return .ultra
} else if device.hasNeuralEngine {
return .high
} else if device.chipGeneration >= .a12 {
return .medium
} else {
return .low
}
}
var shaderComplexity: ShaderComplexity {
switch self {
case .low:
return ShaderComplexity(
maxIterations: 4,
sampleCount: 4,
enableAdvancedEffects: false
)
case .medium:
return ShaderComplexity(
maxIterations: 8,
sampleCount: 8,
enableAdvancedEffects: true
)
case .high:
return ShaderComplexity(
maxIterations: 16,
sampleCount: 16,
enableAdvancedEffects: true
)
case .ultra:
return ShaderComplexity(
maxIterations: 32,
sampleCount: 32,
enableAdvancedEffects: true
)
}
}
}
Caching Strategies
actor ShaderCache {
private var compiledShaders: [String: Shader] = [:]
private let cacheLimit = 50
func shader(named name: String, parameters: ShaderParameters) async throws -> Shader {
let key = "\(name)-\(parameters.hashValue)"
if let cached = compiledShaders[key] {
return cached
}
let shader = ShaderLibrary[dynamicMember: name](parameters)
if #available(iOS 18.0, *) {
try await shader.compile(as: .colorEffect)
}
compiledShaders[key] = shader
if compiledShaders.count > cacheLimit {
let oldestKey = compiledShaders.keys.first!
compiledShaders.removeValue(forKey: oldestKey)
}
return shader
}
}
- Metal System Trace: GPU timeline analysis
- Metal Debugger: Frame capture and analysis
- Instruments: Custom shader profiling
- Xcode GPU Report: Real-time performance metrics
Reusable Architecture
Base Shader Protocol
protocol ShaderEffect {
associatedtype Parameters
static var name: String { get }
static var defaultParameters: Parameters { get }
func shader(with parameters: Parameters) -> Shader
func modifier(with parameters: Parameters) -> some ViewModifier
}
struct GlowEffect: ShaderEffect {
static let name = "glow"
static let defaultParameters = GlowParameters()
struct GlowParameters {
var radius: Float = 10.0
var intensity: Float = 1.0
var color: Color = .white
}
func shader(with parameters: GlowParameters) -> Shader {
ShaderLibrary.glow(
.float(parameters.radius),
.float(parameters.intensity),
.color(parameters.color)
)
}
func modifier(with parameters: GlowParameters) -> some ViewModifier {
GlowModifier(parameters: parameters)
}
}
Component Library Structure
ShaderComponents/
├── Effects/
│ ├── GlowEffect.swift
│ ├── BlurEffect.swift
│ └── DistortionEffect.swift
├── Materials/
│ ├── GlassMaterial.swift
│ ├── MetalMaterial.swift
│ └── FabricMaterial.swift
├── Animations/
│ ├── PulseAnimation.swift
│ ├── WaveAnimation.swift
│ └── MorphAnimation.swift
└── Utilities/
├── ShaderCache.swift
├── PerformanceMonitor.swift
└── DeviceCapabilities.swift
Debugging and Profiling
Debug Overlay System
struct ShaderDebugOverlay: ViewModifier {
@State private var fps: Double = 0
@State private var gpuTime: Double = 0
@State private var drawCalls: Int = 0
func body(content: Content) -> some View {
content
.overlay(alignment: .topTrailing) {
if ProcessInfo.processInfo.environment["SHADER_DEBUG"] == "1" {
VStack(alignment: .trailing, spacing: 2) {
Text("FPS: \(fps, specifier: "%.1f")")
Text("GPU: \(gpuTime, specifier: "%.2f")ms")
Text("Draw: \(drawCalls)")
}
.font(.system(size: 10, design: .monospaced))
.foregroundColor(.green)
.padding(4)
.background(Color.black.opacity(0.8))
.cornerRadius(4)
}
}
}
}
- Texture Sampling Overhead: Minimize texture reads
- Branch Divergence: Avoid conditionals in hot paths
- Register Pressure: Reduce variable usage
- Memory Bandwidth: Optimize data access patterns