【Pyto】iPhoneのTaptic EngineをPythonで操作する

## はじめに
 iPhoneの中にはTaptic Engineという装置が搭載されている機種があります。これは、ホームボタンが物理ボタンでなくなった代わりに押した感触を発生させる装置です。ホームボタン以外にも何かを長押しした時や支払いが完了した時などにクリック感が発生します。

 このTaptic Engineを[Pyto](https://pyto.app)というアプリを用いて操作してみました。

## 環境
iPhone SE (2020)（iOS13.4.1)
Pyto（11.9）

## Taptic EngineとHaptic Feedback
 私はTaptic Engineがクリック感を発生させる装置、そのクリック感をHaptic Feedback（触覚フィードバック）と理解しています。

## Haptic Feedbackの種類
iPhoneのHaptic Feedbackには3つの種類があります。[こちら](https://qiita.com/WorldDownTown/items/2b5a72e41a95763727bb)[1]のサイトを参考にしました。[こちら](https://note.com/tdksk/n/nb4498e59dcad)[2]のサイトにも詳しく書いてあります。

| 種類 | 機能 |
|:----:|:----:|
|UIImpactFeedbackGenerator|1回反応する。3段階の強さが選べる|
|UISelectionFeedbackGenerator|選択系のUI用。一番弱い反応|
|UINotificationFeedbackGenerator|成功失敗等の通知用。3つの種類がある|

## Pytoでの書き方
 Pytoでは`UIKit`が使えます。これはPythonistaでいう`objc_util`のような感じで、Objective-Cを触ることのできる機能です。実際は`rubicon-objc`というライブラリであり、これを使ってObjective-Cを操作しているみたいです。[PytoのUIKitのソースコード](https://github.com/ColdGrub1384/Pyto/blob/045afe13e4b482cd51b77b922000c631e0b0ce35/Lib/UIKit.py)

 `prepare()`は呼び出す前に入れると遅延を最小にすることができるそうですが、オプションです。私が試した感じでは、入れても入れなくても違いは分かりませんでした。[prepareのドキュメント](https://developer.apple.com/documentation/uikit/uifeedbackgenerator/2369818-prepare?language=objc)

### UIImpactFeedbackGenerator

 3段階で強さを調整できます。0が最も弱いです。

```python
import UIKit

generator = UIKit.UIImpactFeedbackGenerator.alloc().init()
generator.prepare()

power = 0  # 0~2の3段階
generator.initWithStyle(power)  # 強さを設定
generator.impactOccurred()  # 発動
```

### UISelectionFeedbackGenerator
強さ調整はできません。

```python
import UIKit

generator = UIKit.UISelectionFeedbackGenerator.alloc().init()
generator.prepare()
generator.selectionChanged()  # 発動
```

### UINotificationFeedbackGenerator
 3種類の反応を選べます。Success(トン**トン**)=0, Warning(**トントン**)=1, Error(**トコトン**)=2です。  

```python
import UIKit

generator = UIKit.UINotificationFeedbackGenerator.alloc().init()
generator.prepare()
mode = 0  # 0~2の3種。
generator.notificationOccurred(mode)  # 発動
```

### 3つを一度に試すプログラム
 上記3種、すべての強さを一度に試すコードです。

```python
import UIKit
import time

generator = UIKit.UIImpactFeedbackGenerator.alloc().init()
generator2 = UIKit.UISelectionFeedbackGenerator.alloc().init()
generator3 = UIKit.UINotificationFeedbackGenerator.alloc().init()

print("impact start")
generator.prepare()
for i in range(3):
    print(f"-{i}")
    for j in range(3):
        generator.initWithStyle(i)
        generator.impactOccurred()
        time.sleep(0.3)
print("finished")
time.sleep(1)

print("selection start")
generator2.prepare()
for i in range(10):
    generator2.selectionChanged()
    time.sleep(0.1)
print("finished")
time.sleep(1)

print("notification start")
generator3.prepare()
for i in range(3):
    print(f"-{i}")
    generator3.notificationOccurred(i)
    time.sleep(1)
print("finished")
```

## おわりに
 `.alloc().init()`と書けば良いことに気付くまで時間がかかりました。


## 参考サイト

[1] https://qiita.com/WorldDownTown/items/2b5a72e41a95763727bb （UIFeedbackGeneratorの使い方と便利に使えるライブラリ)
[2] https://note.com/tdksk/n/nb4498e59dcad （iPhone アプリでの Haptic Feedback による効果的なマイクロインタラクション）
[3] https://qiita.com/griffin_stewie/items/298f57ca3f1714ebe45c （iPhone 7 を入手したので早速 Taptic Engine の API を試してみました）
[4] https://developer.apple.com/documentation/uikit/uifeedbackgenerator?language=objc#2555399 （UIFeedbackGenerator）
[5] https://rubicon-objc.readthedocs.io/en/latest/tutorial/tutorial-2.html （Rubicon Tutorial 2)