欢迎来到本系列教程的第四十七篇。在前四十六篇文章中,你已经学习了从Swift基础到导航架构的全方位iOS开发技能。现在,你能够构建出功能完善、架构清晰的专业应用了。但是,你的应用能否提供更沉浸式的体验?当用户完成任务时,能否感受到庆祝的振动?当操作出错时,能否通过触感获得即时反馈?
Core Haptics是苹果提供的触感引擎框架,它让开发者能够创建丰富、自定义的触觉反馈模式。从简单的轻触到复杂的音乐同步振动,Core Haptics能让你的应用与用户建立起更深层次的联系。
在这一篇中,你将学到:
-
触感反馈基础
-
UIImpactFeedbackGenerator
-
UINotificationFeedbackGenerator
-
UISelectionFeedbackGenerator
-
-
Core Haptics进阶
-
引擎配置
-
模式设计
-
自定义波形
-
-
触感与动画结合
-
手势同步触感
-
动画曲线匹配
-
-
音乐触感
-
音频波形转触感
-
节拍同步
-
-
实战项目:构建一个触感丰富的音乐节奏游戏
一、触感反馈基础
1.1 UIFeedbackGenerator概览
swift
import SwiftUI
import UIKit
// MARK: - 基础触感反馈管理器
class HapticManager {
static let shared = HapticManager()
// 轻触反馈
func lightImpact() {
let generator = UIImpactFeedbackGenerator(style: .light)
generator.impactOccurred()
}
// 中等触感
func mediumImpact() {
let generator = UIImpactFeedbackGenerator(style: .medium)
generator.impactOccurred()
}
// 重触感
func heavyImpact() {
let generator = UIImpactFeedbackGenerator(style: .heavy)
generator.impactOccurred()
}
// 软触感
func softImpact() {
let generator = UIImpactFeedbackGenerator(style: .soft)
generator.impactOccurred()
}
// 硬触感
func rigidImpact() {
let generator = UIImpactFeedbackGenerator(style: .rigid)
generator.impactOccurred()
}
// 成功通知
func successNotification() {
let generator = UINotificationFeedbackGenerator()
generator.notificationOccurred(.success)
}
// 警告通知
func warningNotification() {
let generator = UINotificationFeedbackGenerator()
generator.notificationOccurred(.warning)
}
// 错误通知
func errorNotification() {
let generator = UINotificationFeedbackGenerator()
generator.notificationOccurred(.error)
}
// 选择反馈
func selectionChanged() {
let generator = UISelectionFeedbackGenerator()
generator.selectionChanged()
}
// 带延迟的准备(提高响应速度)
func prepareForImpact() {
let generator = UIImpactFeedbackGenerator(style: .medium)
generator.prepare()
}
}
// MARK: - 使用示例
struct FeedbackDemoView: View {
var body: some View {
List {
Section("冲击反馈") {
Button("轻触") { HapticManager.shared.lightImpact() }
Button("中等") { HapticManager.shared.mediumImpact() }
Button("重触") { HapticManager.shared.heavyImpact() }
Button("软触") { HapticManager.shared.softImpact() }
Button("硬触") { HapticManager.shared.rigidImpact() }
}
Section("通知反馈") {
Button("成功") { HapticManager.shared.successNotification() }
Button("警告") { HapticManager.shared.warningNotification() }
Button("错误") { HapticManager.shared.errorNotification() }
}
Section("选择反馈") {
Button("选择变化") { HapticManager.shared.selectionChanged() }
}
}
.navigationTitle("触感反馈演示")
}
}
二、Core Haptics进阶
2.1 Core Haptics基础配置
swift
import CoreHaptics
import SwiftUI
// MARK: - Core Haptics管理器
class CoreHapticsManager: ObservableObject {
private var engine: CHHapticEngine?
@Published var isSupported = false
init() {
isSupported = CHHapticEngine.capabilitiesForHardware().supportsHaptics
if isSupported {
setupEngine()
}
}
private func setupEngine() {
do {
engine = try CHHapticEngine()
try engine?.start()
// 引擎停止时自动重启
engine?.stoppedHandler = { reason in
print("引擎停止: \(reason)")
try? self.engine?.start()
}
// 引擎重置时重新配置
engine?.resetHandler = {
print("引擎重置")
self.setupEngine()
}
} catch {
print("初始化触感引擎失败: \(error)")
}
}
// 简单振动模式
func playSimpleHaptic() {
guard isSupported, let engine = engine else { return }
let intensity = CHHapticEventParameter(parameterID: .hapticIntensity, value: 1.0)
let sharpness = CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.5)
let event = CHHapticEvent(
eventType: .hapticTransient,
parameters: [intensity, sharpness],
relativeTime: 0
)
do {
let pattern = try CHHapticPattern(events: [event], parameters: [])
let player = try engine.makePlayer(with: pattern)
try player.start(atTime: 0)
} catch {
print("播放触感失败: \(error)")
}
}
// 连续振动
func playContinuousHaptic(duration: TimeInterval, intensity: Float, sharpness: Float) {
guard isSupported, let engine = engine else { return }
let intensityParam = CHHapticEventParameter(parameterID: .hapticIntensity, value: intensity)
let sharpnessParam = CHHapticEventParameter(parameterID: .hapticSharpness, value: sharpness)
let event = CHHapticEvent(
eventType: .hapticContinuous,
parameters: [intensityParam, sharpnessParam],
relativeTime: 0,
duration: duration
)
do {
let pattern = try CHHapticPattern(events: [event], parameters: [])
let player = try engine.makePlayer(with: pattern)
try player.start(atTime: 0)
} catch {
print("播放连续触感失败: \(error)")
}
}
}
2.2 自定义触感模式
swift
// MARK: - 自定义触感模式
protocol HapticPattern {
var pattern: CHHapticPattern { get }
}
struct PulsePattern: HapticPattern {
let count: Int
let intensity: Float
let sharpness: Float
var pattern: CHHapticPattern {
var events: [CHHapticEvent] = []
for i in 0..<count {
let event = CHHapticEvent(
eventType: .hapticTransient,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: intensity),
CHHapticEventParameter(parameterID: .hapticSharpness, value: sharpness)
],
relativeTime: Double(i) * 0.1
)
events.append(event)
}
return try! CHHapticPattern(events: events, parameters: [])
}
}
struct RampPattern: HapticPattern {
let duration: TimeInterval
let startIntensity: Float
let endIntensity: Float
let startSharpness: Float
let endSharpness: Float
var pattern: CHHapticPattern {
let intensityParam = CHHapticParameterCurve.ControlPoint(relativeTime: 0, value: startIntensity)
let intensityParamEnd = CHHapticParameterCurve.ControlPoint(relativeTime: duration, value: endIntensity)
let intensityCurve = CHHapticParameterCurve(
parameterID: .hapticIntensityControl,
controlPoints: [intensityParam, intensityParamEnd],
relativeTime: 0
)
let sharpnessParam = CHHapticParameterCurve.ControlPoint(relativeTime: 0, value: startSharpness)
let sharpnessParamEnd = CHHapticParameterCurve.ControlPoint(relativeTime: duration, value: endSharpness)
let sharpnessCurve = CHHapticParameterCurve(
parameterID: .hapticSharpnessControl,
controlPoints: [sharpnessParam, sharpnessParamEnd],
relativeTime: 0
)
let event = CHHapticEvent(
eventType: .hapticContinuous,
parameters: [],
relativeTime: 0,
duration: duration
)
return try! CHHapticPattern(events: [event], parameterCurves: [intensityCurve, sharpnessCurve])
}
}
// MARK: - 扩展CoreHapticsManager
extension CoreHapticsManager {
func playPulse(count: Int, intensity: Float = 1.0, sharpness: Float = 0.5) {
guard isSupported, let engine = engine else { return }
let pattern = PulsePattern(count: count, intensity: intensity, sharpness: sharpness)
do {