I\'m trying to implement haptic feedback at the beginning of a tap for a Button in SwiftUI. Therefore I\'m trying to use simultaneousGesture, but I\'m sill struggling. I can\'t
I have a wrapper view that plays a haptic event, and then calls the action:
struct HapticButton: View {
let content: String
let action: () -> Void
init(_ content: String, _ action: @escaping () -> Void) {
self.content = content
self.action = action
}
var body: some View {
Button(content) {
HapticService.shared.play(event: .buttonTap)
self.action()
}
}
}
Used like:
HapticButton("Press me") { print("hello") } // plays haptic and prints
pure SwiftUI solution below:
HStack {
Image("icon")
Text("Login")
}
.onTouchGesture(
touchBegan: { self.generateHapticFeedback = true },
touchEnd: { _ in self.generateHapticFeedback = false }
)
Just add this snippet somewhere in your project:
struct TouchGestureViewModifier: ViewModifier {
let touchBegan: () -> Void
let touchEnd: (Bool) -> Void
@State private var hasBegun = false
@State private var hasEnded = false
private func isTooFar(_ translation: CGSize) -> Bool {
let distance = sqrt(pow(translation.width, 2) + pow(translation.height, 2))
return distance >= 20.0
}
func body(content: Content) -> some View {
content.gesture(DragGesture(minimumDistance: 0)
.onChanged { event in
guard !self.hasEnded else { return }
if self.hasBegun == false {
self.hasBegun = true
self.touchBegan()
} else if self.isTooFar(event.translation) {
self.hasEnded = true
self.touchEnd(false)
}
}
.onEnded { event in
if !self.hasEnded {
let success = !self.isTooFar(event.translation)
self.touchEnd(success)
}
self.hasBegun = false
self.hasEnded = false
})
}
}
extension View {
func onTouchGesture(touchBegan: @escaping () -> Void = {},
touchEnd: @escaping (Bool) -> Void = { _ in }) -> some View {
modifier(TouchGestureViewModifier(touchBegan: touchBegan, touchEnd: touchEnd))
}
}
You can use UIFeedbackGenerator like this:
let generator = UINotificationFeedbackGenerator()
generator.notificationOccurred(.error)
Or, as you're using SwiftUI, you'll be able to use CoreHaptics like this:
let engine = try CHHapticEngine()
try engine.start()
let hapticEvent = CHHapticEvent(eventType: .hapticTransient, parameters: [
CHHapticEventParameter(parameterID: .hapticSharpness, value: sharpness), CHHapticEventParameter(parameterID: .hapticIntensity, value: intensity),
], relativeTime: 0)
let audioEvent = CHHapticEvent(eventType: .audioContinuous, parameters: [
CHHapticEventParameter(parameterID: .audioVolume, value: volume),
CHHapticEventParameter(parameterID: .decayTime, value: decay),
CHHapticEventParameter(parameterID: .sustained, value: 0),
], relativeTime: 0)
let pattern = try CHHapticPattern(events: [hapticEvent, audioEvent], parameters: [])
let hapticPlayer = try engine.makePlayer(with: pattern)
try hapticPlayer?.start(atTime: CHHapticTimeImmediate)
As I know, this is the most simplest way to get haptic feedback in SwiftUI
When tapping a button :
Button(action: {
let impactMed = UIImpactFeedbackGenerator(style: .medium)
impactMed.impactOccurred()
}) {
Text("This is a Button")
}
You can change the intensity of the haptic feedback by replacing .medium
with .soft
, .light
, .heavy
, or .rigid
or when tapping anything else :
.onTapGesture {
let impactHeavy = UIImpactFeedbackGenerator(style: .heavy)
impactHeavy.impactOccurred()
}
If you want to make something like Haptic Touch, replace .onTapGesture
with .onLongPressGesture
like this
.onLongPressGesture {
let impactHeavy = UIImpactFeedbackGenerator(style: .heavy)
impactHeavy.impactOccurred()
}