【SwiftUI】長押しで反応するボタンを作る

お疲れ様です。
手が悴んでキーボードが打てません。ハンドウォーマーってのを買おうと思ってます。
スクリーンに張り付いているそこのあなた、あなたも手と腰は労ってくださいまし。

ーーーーーーーーーーーー

SwiftUIネタですが、ボタンを長押ししたらiPhoneのホームボタンのようにバイブレーションさせてアクション起こしたいケースがありました。
わざわざ残さなくてもサンプル転がってますが、備忘録程度に残しておこうと思います。

ボタンを長押しで反応させ振動を与える

What is Haptic Touch?

ホームボタンを押すと、ボタンではないのに押した感覚を得られるアレ。
どうやらHaptic Touchというらしい、知らなかった。

感覚的には強く押し込んで押した感じ(振動)を得ているようですが、内部的にはロングタップ+振動で表現しているようですね。
圧力を見る3Dタッチというのがありましたが、近年の端末iPhone11以降はこれがなくなり上記の「触覚タッチ」というものになっているようです。

実装

SwiftUIには .onLongPressGesture(perform: ) というジェスチャーがあるので、単純に考えればButtonに付けてあげればよさそうですが、どうもこれだとやりたいことができないようでした。
既存のボタンアクションに反応を取られているようで、onLongPressが拾わない。

なので解決策としては以下で実現できました。

Button {

// didTap action

} label: {
    Text("some texts")
}
.padding(.vertical, 8)
.simultaneousGesture(
    LongPressGesture(minimumDuration: 0.2)
        .onEnded({ _ in
            let hapticTouch = UIImpactFeedbackGenerator(style: .heavy)
            hapticTouch.impactOccurred()
        })
)

simultaneousGestureという同時にジェスチャー反応させるよ、というのがあるので、この内部にバイブ処理を入れて、アクションは通常通りButton内部に入れ込む形です。

UIImpactFeedbackGeneratorは入れるだけで振動を得られるので、強さは任意で変更してください。

ちなみに、Image()で作る擬似ボタンの場合は.onLongPressでも検知します。
状況に応じて使い分けてください。

Image("foo")
    .resizable()
    .scaledToFit()
    .frame(width: 100, height: 100)
    .onLongPressGesture {
        let hapticTouch = UIImpactFeedbackGenerator(style: .heavy)
        hapticTouch.impactOccurred()

    } onPressingChanged: { onChange in
        if onChange {
            // do something
        }
    }

以上です。

終わりに

探せばやり方はたくさん落ちていますが、SwiftUIに関しては記事に起こすかどうかは少し緩め?でメモ要素強めで書いていこうと思っております、はい。