๐ŸŒ KO

๐Ÿ“ณ 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

๐ŸŽฏ Practical Usage

๐Ÿ“š 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.

๐Ÿ“Ž Apple Official Resources

๐Ÿ“˜ Documentation ๐Ÿ’ป Sample Code ๐ŸŽฌ WWDC Sessions