๐ณ Core Haptics
โญ Difficulty: โญโญ
โฑ๏ธ Est. Time: 1h
๐ Graphics & Media
์ ๊ตํ ์ด๊ฐ ํผ๋๋ฐฑ ๋์์ธ ํ๋ ์์ํฌ
iOS 13+Taptic Engine
โจ Core Haptics?
Core Haptics is a framework that leverages the iPhone's Taptic Engine to create sophisticated, customizable haptic feedback. Beyond simple vibrations, you can adjust intensity, sharpness, and duration to deliver immersive tactile experiences in games, apps, and user interactions. Complex haptic patterns can be saved and played back via AHAP files.
๐ก Key Features: Custom Haptic Patterns ยท Continuous/Transient Events ยท Intensity Control ยท Sharpness Control ยท AHAP File Support ยท Audio-Haptic Sync ยท Dynamic Parameters
๐ฎ 1. CHHapticEngine ์ด๊ธฐํ
CHHapticEngine์ ์์ฑํ๊ณ ์์.
HapticManager.swift โ ์์ง ์ด๊ธฐํ
import CoreHaptics import SwiftUI @Observable class HapticManager { private var engine: CHHapticEngine? var supportsHaptics = false init() { prepareHaptics() } // ํ ํฑ ์์ง ์ค๋น func prepareHaptics() { // ๋๋ฐ์ด์ค๊ฐ ํ ํฑ์ ์ง์ํ๋์ง ํ์ธ guard CHHapticEngine.capabilitiesForHardware().supportsHaptics else { print("โ ๏ธ ํ ํฑ ๋ฏธ์ง์ ๋๋ฐ์ด์ค") return } do { // ์์ง ์์ฑ engine = try CHHapticEngine() supportsHaptics = true // ์์ง ์์ try engine?.start() // ์์ง ์ ์ง ํธ๋ค๋ฌ engine?.stoppedHandler = { reason in print("โ ๏ธ ์์ง ์ ์ง: \(reason)") } // ์์ง ๋ฆฌ์ ํธ๋ค๋ฌ engine?.resetHandler = { [weak self] in print("๐ ์์ง ๋ฆฌ์ ") do { try self?.engine?.start() } catch { print("โ ์์ง ์ฌ์์ ์คํจ: \(error)") } } print("โ ํ ํฑ ์์ง ์ค๋น ์๋ฃ") } catch { print("โ ํ ํฑ ์์ง ์์ฑ ์คํจ: \(error)") } } // ์์ง ์์ func startEngine() { guard supportsHaptics else { return } do { try engine?.start() } catch { print("โ ์์ง ์์ ์คํจ: \(error)") } } // ์์ง ์ ์ง func stopEngine() { engine?.stop() } }
๐ฅ 2. ์๊ฐ ํ ํฑ ์ด๋ฒคํธ
Create Transient events for momentary taps or impacts.
TransientHaptics.swift โ Transient Haptics
import CoreHaptics extension HapticManager { // ๊ธฐ๋ณธ ํญ ํ ํฑ func playSimpleTap() { guard supportsHaptics, let engine = engine else { return } do { // ์๊ฐ ์ด๋ฒคํธ ์์ฑ let event = CHHapticEvent( eventType: .hapticTransient, parameters: [ CHHapticEventParameter(parameterID: .hapticIntensity, value: 1.0), CHHapticEventParameter(parameterID: .hapticSharpness, value: 1.0) ], relativeTime: 0 ) // ํจํด ์์ฑ ๋ฐ ์ฌ์ let pattern = try CHHapticPattern(events: [event], parameters: []) let player = try engine.makePlayer(with: pattern) try player.start(atTime: 0) print("โ ํญ ํ ํฑ ์ฌ์") } catch { print("โ ํ ํฑ ์ฌ์ ์คํจ: \(error)") } } // ์ปค์คํ ๊ฐ๋์ ํญ func playTap(intensity: Float, sharpness: Float) { guard supportsHaptics, let engine = engine else { return } do { let event = CHHapticEvent( eventType: .hapticTransient, parameters: [ CHHapticEventParameter(parameterID: .hapticIntensity, value: intensity), CHHapticEventParameter(parameterID: .hapticSharpness, value: sharpness) ], relativeTime: 0 ) let pattern = try CHHapticPattern(events: [event], parameters: []) let player = try engine.makePlayer(with: pattern) try player.start(atTime: 0) } catch { print("โ ํ ํฑ ์ฌ์ ์คํจ: \(error)") } } // ์ฐ์ ํญ ํจํด func playDoubleTap() { guard supportsHaptics, let engine = engine else { return } do { let events = [ CHHapticEvent( eventType: .hapticTransient, parameters: [ CHHapticEventParameter(parameterID: .hapticIntensity, value: 1.0), CHHapticEventParameter(parameterID: .hapticSharpness, value: 1.0) ], relativeTime: 0 ), CHHapticEvent( eventType: .hapticTransient, parameters: [ CHHapticEventParameter(parameterID: .hapticIntensity, value: 1.0), CHHapticEventParameter(parameterID: .hapticSharpness, value: 1.0) ], relativeTime: 0.2 // 0.2์ด ํ ๋ ๋ฒ์งธ ํญ ) ] let pattern = try CHHapticPattern(events: events, parameters: []) let player = try engine.makePlayer(with: pattern) try player.start(atTime: 0) print("โ ๋๋ธ ํญ ํ ํฑ ์ฌ์") } catch { print("โ ํ ํฑ ์ฌ์ ์คํจ: \(error)") } } }
๐ 3. ์ฐ์ ํ ํฑ ์ด๋ฒคํธ
Create Continuous events for sustained vibrations.
ContinuousHaptics.swift โ Continuous Haptics
import CoreHaptics extension HapticManager { // ์ฐ์ ์ง๋ (1์ด) func playContinuousHaptic(duration: TimeInterval = 1.0) { guard supportsHaptics, let engine = engine else { return } do { // ์ฐ์ ์ด๋ฒคํธ ์์ฑ let event = CHHapticEvent( eventType: .hapticContinuous, parameters: [ CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.7), CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.5) ], relativeTime: 0, duration: duration ) let pattern = try CHHapticPattern(events: [event], parameters: []) let player = try engine.makePlayer(with: pattern) try player.start(atTime: 0) print("โ ์ฐ์ ํ ํฑ ์ฌ์") } catch { print("โ ํ ํฑ ์ฌ์ ์คํจ: \(error)") } } // ๊ฐ๋๊ฐ ๋ณํ๋ ์ฐ์ ํ ํฑ func playFadingHaptic() { guard supportsHaptics, let engine = engine else { return } do { // ๊ฐ๋๊ฐ ์ ์ฐจ ๊ฐ์ํ๋ ์ฐ์ ์ด๋ฒคํธ let event = CHHapticEvent( eventType: .hapticContinuous, parameters: [ CHHapticEventParameter(parameterID: .hapticIntensity, value: 1.0), CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.5) ], relativeTime: 0, duration: 2.0 ) // ๊ฐ๋ ๊ฐ์ ๋งค๊ฐ๋ณ์ ์ปค๋ธ let parameterCurve = CHHapticParameterCurve( parameterID: .hapticIntensityControl, controlPoints: [ CHHapticParameterCurve.ControlPoint(relativeTime: 0, value: 1.0), CHHapticParameterCurve.ControlPoint(relativeTime: 2.0, value: 0.0) ], relativeTime: 0 ) let pattern = try CHHapticPattern( events: [event], parameterCurves: [parameterCurve] ) let player = try engine.makePlayer(with: pattern) try player.start(atTime: 0) print("โ ํ์ด๋ ์์ ํ ํฑ ์ฌ์") } catch { print("โ ํ ํฑ ์ฌ์ ์คํจ: \(error)") } } // ํ์ค ํจํด (๋ฐ๋) func playPulsePattern() { guard supportsHaptics, let engine = engine else { return } do { var events: [CHHapticEvent] = [] // 5๋ฒ์ ํ์ค ์์ฑ for i in 0..<5 { let event = CHHapticEvent( eventType: .hapticTransient, parameters: [ CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.8), CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.5) ], relativeTime: TimeInterval(i) * 0.3 ) events.append(event) } let pattern = try CHHapticPattern(events: events, parameters: []) let player = try engine.makePlayer(with: pattern) try player.start(atTime: 0) print("โ ํ์ค ํจํด ์ฌ์") } catch { print("โ ํ ํฑ ์ฌ์ ์คํจ: \(error)") } } }
๐ 4. AHAP ํ์ผ
Save and load complex haptic patterns as AHAP files.
AHAPLoader.swift โ Load AHAP Files
import CoreHaptics extension HapticManager { // AHAP ํ์ผ์์ ํจํด ๋ก๋ func playAHAPFile(named fileName: String) { guard supportsHaptics, let engine = engine else { return } do { // AHAP ํ์ผ ๊ฒฝ๋ก ๊ฐ์ ธ์ค๊ธฐ guard let url = Bundle.main.url( forResource: fileName, withExtension: "ahap" ) else { print("โ AHAP ํ์ผ์ ์ฐพ์ ์ ์์") return } // ํจํด ์์ฑ let pattern = try CHHapticPattern(contentsOf: url) // ํ๋ ์ด์ด ์์ฑ ๋ฐ ์ฌ์ let player = try engine.makePlayer(with: pattern) try player.start(atTime: 0) print("โ AHAP ํ์ผ ์ฌ์: \(fileName)") } catch { print("โ AHAP ํ์ผ ์ฌ์ ์คํจ: \(error)") } } // AHAP ํจํด์ ํ์ผ๋ก ์ ์ฅ func savePattern(_ pattern: CHHapticPattern, to fileName: String) { do { // ํจํด์ ๋์ ๋๋ฆฌ๋ก ๋ณํ let dictionary = pattern.exportDictionary() // JSON ๋ฐ์ดํฐ ์์ฑ let jsonData = try JSONSerialization.data( withJSONObject: dictionary, options: .prettyPrinted ) // ํ์ผ๋ก ์ ์ฅ let url = FileManager.default .urls(for: .documentDirectory, in: .userDomainMask)[0] .appendingPathComponent("\(fileName).ahap") try jsonData.write(to: url) print("โ AHAP ํ์ผ ์ ์ฅ: \(url)") } catch { print("โ AHAP ํ์ผ ์ ์ฅ ์คํจ: \(error)") } } } // AHAP ํ์ผ ์์ ๊ตฌ์กฐ (JSON) /* { "Pattern": [ { "Event": { "Time": 0.0, "EventType": "HapticTransient", "EventParameters": [ { "ParameterID": "HapticIntensity", "ParameterValue": 1.0 }, { "ParameterID": "HapticSharpness", "ParameterValue": 1.0 } ] } } ] } */
๐จ 5. ๊ฒ์์ฉ ํ ํฑ ํจํด
Implement various haptic patterns for use in games.
GameHaptics.swift โ ๊ฒ์์ฉ ํ
ํฑ
import CoreHaptics extension HapticManager { // ์ถฉ๋ ํจ๊ณผ func playCollisionHaptic(intensity: Float) { guard supportsHaptics, let engine = engine else { return } do { let event = CHHapticEvent( eventType: .hapticTransient, parameters: [ CHHapticEventParameter(parameterID: .hapticIntensity, value: intensity), CHHapticEventParameter(parameterID: .hapticSharpness, value: 1.0) ], relativeTime: 0 ) let pattern = try CHHapticPattern(events: [event], parameters: []) let player = try engine.makePlayer(with: pattern) try player.start(atTime: 0) } catch { print("โ ์ถฉ๋ ํ ํฑ ์คํจ: \(error)") } } // ํญ๋ฐ ํจ๊ณผ func playExplosionHaptic() { guard supportsHaptics, let engine = engine else { return } do { // ๊ฐํ ์ด๊ธฐ ์ถฉ๊ฒฉ let impact = CHHapticEvent( eventType: .hapticTransient, parameters: [ CHHapticEventParameter(parameterID: .hapticIntensity, value: 1.0), CHHapticEventParameter(parameterID: .hapticSharpness, value: 1.0) ], relativeTime: 0 ) // ์ฌ์ง let rumble = CHHapticEvent( eventType: .hapticContinuous, parameters: [ CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.5), CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.3) ], relativeTime: 0.1, duration: 0.5 ) let pattern = try CHHapticPattern(events: [impact, rumble], parameters: []) let player = try engine.makePlayer(with: pattern) try player.start(atTime: 0) } catch { print("โ ํญ๋ฐ ํ ํฑ ์คํจ: \(error)") } } // ์์ง ์๋ฆฌ (์ง์์ ์ธ ์ง๋) func playEngineHaptic() { guard supportsHaptics, let engine = engine else { return } do { let event = CHHapticEvent( eventType: .hapticContinuous, parameters: [ CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.6), CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.2) ], relativeTime: 0, duration: 3.0 ) let pattern = try CHHapticPattern(events: [event], parameters: []) let player = try engine.makePlayer(with: pattern) try player.start(atTime: 0) } catch { print("โ ์์ง ํ ํฑ ์คํจ: \(error)") } } }
๐ฑ Complete Example
HapticDemoView.swift โ Haptic Demo App
import SwiftUI struct HapticDemoView: View { @State private var hapticManager = HapticManager() @State private var intensity: Float = 0.5 @State private var sharpness: Float = 0.5 var body: some View { NavigationStack { Form { Section("๊ธฐ๋ณธ ํ ํฑ") { Button("๋จ์ ํญ") { hapticManager.playSimpleTap() } Button("๋๋ธ ํญ") { hapticManager.playDoubleTap() } Button("์ฐ์ ์ง๋") { hapticManager.playContinuousHaptic() } } Section("์ปค์คํ ํ ํฑ") { VStack(alignment: .leading, spacing: 12) { Text("๊ฐ๋: \(String(format: "%.2f", intensity))") Slider(value: $intensity, in: 0...1) Text("์ ๋ช ๋: \(String(format: "%.2f", sharpness))") Slider(value: $sharpness, in: 0...1) Button("ํ ์คํธ") { hapticManager.playTap(intensity: intensity, sharpness: sharpness) } .frame(maxWidth: .infinity) .buttonStyle(.borderedProminent) } } Section("ํจํด") { Button("ํ์ด๋ ์์") { hapticManager.playFadingHaptic() } Button("ํ์ค ํจํด") { hapticManager.playPulsePattern() } } Section("๊ฒ์ ํจ๊ณผ") { Button("์ถฉ๋") { hapticManager.playCollisionHaptic(intensity: 0.8) } Button("ํญ๋ฐ") { hapticManager.playExplosionHaptic() } Button("์์ง ์๋ฆฌ") { hapticManager.playEngineHaptic() } } Section { Text("ํ ํฑ ์ง์: \(hapticManager.supportsHaptics ? "โ " : "โ")") .foregroundStyle(.secondary) } } .navigationTitle("ํ ํฑ ๋ฐ๋ชจ") } } }
๐ก HIG Guidelines
- ์ ์ ํ ์ฌ์ฉ: ์๋ฏธ ์๋ ์ธํฐ๋์ ์๋ง ํ ํฑ ์ฌ์ฉ
- ๊ณผ๋ํ ์ฌ์ฉ ๊ธ์ง: ๋๋ฌด ๋น๋ฒํ ํ ํฑ์ ์ฌ์ฉ์๋ฅผ ํผ๋กํ๊ฒ ํจ
- ๋๋ฐ์ด์ค ํ์ธ: ํ ํฑ Supported ์ฌ๋ถ ํ์ธ ํ์
- ์ค์ ์กด์ค: ์์คํ ํ ํฑ ์ค์ ์กด์ค
- ์ค๋์ค์ ์กฐํ: ์ฌ์ด๋์ ํ ํฑ์ ํจ๊ป ์ฌ์ฉํ๋ฉด ๋ ๊ฐ๋ ฅํ ํจ๊ณผ
๐ฏ Practical Usage
- Games: ์ถฉ๋, ํญ๋ฐ, ๋ฐ์ฌ ๋ฑ์ ๋ฌผ๋ฆฌ์ ํผ๋๋ฐฑ
- UI ์ธํฐ๋์ : ๋ฒํผ ํญ, ์ค์์น ํ ๊ธ, ์ฌ๋ผ์ด๋ ์กฐ์
- ์๋ฆผ: ์ค์ํ ์ด๋ฒคํธ์ ๋ํ ์ด๊ฐ ์๋ฆผ
- Media Apps: ์์ ๋นํธ์ ๋ง์ถ ํ ํฑ
- ์ ๊ทผ์ฑ: ์๊ฐ ์ฅ์ ์ธ์ ์ํ ์ด๊ฐ ํผ๋๋ฐฑ
๐ Learn More
โก๏ธ Performance Tips: The haptic engine automatically stops when the app goes to background. When returning to foreground,
resetHandler to auto-restart. Using AHAP files lets you efficiently manage complex patterns.