はじめに
iOSエンジニアの名原です。 9月1日から9月3日の3日間で開催されたiOSDC2023に、オンラインで参加しました。
たくさん興味深いテーマがありましたが、今回は特に印象に残ったテーマCoreHapticsについて、自分でもサンプルを作ってみたので紹介したいと思います。
登壇者様の発表
Haptics(ハプティクス)とは、振動で”触覚”を人工的に作り出し、疑似的に再現する技術のことで、スマホのボタンをタップした時に端末が「ププッ」と振動したりするアレです。
この発表では、CoreHapticsの使い方と設定できるパラメータ、効果的な使い方について説明がありました。図を多く交えて説明してくださっているので、スライド見るだけでも分かりやすいと思います。
Hapticsを効果的に使うための注意点!
Hapticsは、強力なフィードバックでUXの向上が期待できますが、気をつけないと逆にユーザ体験を邪魔してしまいます。
以下の内容は発表の締めにベストプラクティスとして紹介がされていたのですが、実際の開発でHapticsを使うなら知っておくべき、デザイナーとも共有したい内容だと思いました。
Appleのヒューマンインターフェースガイドラインに詳しく記載があるので、要チェックだと思います。
一貫性を持たせる
- ”特定の操作に対して特定の振動”というのが決まっていないとユーザがびっくりします。
他のフィードバックを補完する使い方をする
- 視覚的、聴覚的な変化に合わせて振動させると、より自然に見えるようになり効果的です。
使いすぎない
- 振動の頻度が多いとアプリを使っていて疲れてしまいます。逆にユーザ体験を低下させます。
オフできるようにする
- 振動を嫌うユーザもいるので、アプリ内に振動をオフにできるようにしておく必要があります。
別の操作を邪魔しない
- カメラなど他の操作の邪魔はしないようタイミングに気を付ける必要があります。
サンプルアプリの紹介
登壇者様が公開しているデモアプリを参考に、サンプルアプリを実装してみました。
作ったのは、CoreHapticsの各パラメータを調整できてボタンタップで発火するサンプルアプリです。また比較のため、UIFeedbackGeneratorを使った基本パターンのHapticsも試せるようにしています。
CoreHapticsとUIFeedbackGenerator
iOSアプリでのHapticsの実現方法としては、以下の2つがあります。CoreHapticsの方が、指定できるパラメータが多いので、シーンに合わせて独自でカスタムしたい場合にはCoreHapticsを使うことになります。
UIFeedbackGenerator(iOS 10.0+)
- 振動の強度、鋭さが異なるいくつかのデフォルトの振動パターンから選択します。
- パターンは、light,medium,heavy、iOS13以上のsoft,rigidの5種類です。
CoreHaptics(iOS13.0+)
- 振動の強度の他にも、振動の長さや振動の増減の時間など、複数のパラメータを指定できます。
- 各パラメータについては、登壇者様の資料で図を交えながら説明してくださっているので、そちらが参考になります。
CoreHapticsの実装手順
- CoreHapticsをimportする
import CoreHaptics
HapticClientを実装する
アプリ起動時にHapticClientを作成して、Engineを用意します。
- ボタン押下時に
play(_:)
を呼び出すことで、Hapticsを発火します。
class HapticClient { private let engine: CHHapticEngine init() throws { engine = try CHHapticEngine() try engine.start() } func play(_ event: CHHapticEvent, at time: TimeInterval = 0) throws { let pattern = try CHHapticPattern(events: [event], parameters: []) let player = try engine.makePlayer(with: pattern) try player.start(atTime: time) } }
- 各パラメータを調整できるようにするためHapticConfigクラスを用意
class HapticConfig: ObservableObject { @Published var isContinuous: Bool = false @Published var intensity: Float = 0.8 @Published var sharpness: Float = 0.5 @Published var relativeTime: Float = 0 @Published var duration: Float = 1 @Published var attackTime: Float = 0.5 @Published var sustained: Bool = false @Published var releaseOrDecayTime: Float = 0.5 }
- HapticConfigクラスの各プロパティを
Slider
,Toggle
などで値を変更できるようにする
private var eventTypeToggle: some View { VStack { Toggle(isOn: $config.isContinuous) { title("継続的", definition: "Continuous") } } } private var intensitySliver: some View { HStack { title("強度", definition: "Intensity") Slider(value: $config.intensity) Text("\(String(format: "%.2f", config.intensity))") } }
- ボタン押下時にHapticEventが発火されるようにする
private func playHaptic() { let event = CHHapticEvent( eventType: config.isContinuous ? .hapticContinuous : .hapticTransient, parameters: [ .init(parameterID: .hapticIntensity, value: config.intensity), .init(parameterID: .hapticSharpness, value: config.sharpness), .init(parameterID: .attackTime, value: config.attackTime), config.sustained ? .init(parameterID: .releaseTime, value: config.releaseOrDecayTime) : .init(parameterID: .decayTime, value: config.releaseOrDecayTime), ], relativeTime: TimeInterval(config.relativeTime), duration: TimeInterval(config.duration) ) try? hapticClient.play(event) }
所感
話を聞きながらデモアプリのソースを見てみると、自分でも出来そうと思い、今回サンプルを作成してみました。
サンプルのような単発の振動であれば、比較的簡単に実装ができましたが、実際のアプリで効果的に使うのはもう一工夫必要そうだなと感じました。
AHAPファイルというPatternの指定をするファイルを組み込むことができるそうなので、こちらも時間があれば試してみたいと思います。
こういったカンファレンスには初めて参加したので、新鮮な知識を得られてとても有意義な時間でした。
また来年もぜひ参加できればと思いました。