【问题标题】:How to create haptic feedback for a Button in SwiftUI?如何在 SwiftUI 中为按钮创建触觉反馈?
【发布时间】:2019-06-25 07:06:14
【问题描述】:

我正在尝试在 SwiftUI 中的按钮点击开始时实现触觉反馈。因此,我正在尝试使用同时手势,但我仍在苦苦挣扎。我不知道什么时候开始点击。

也没有为 Swift UI 实现触觉反馈,所以我想我会从 UIKit 中混合它?

我尝试实现 TapGesture 的更新方法,但它似乎没有做任何事情。这就是我到目前为止所得到的。感谢您的任何提示。

struct HapticButton : View {

    @GestureState var isDetectingTap = false

    var body: some View {

        let tap = TapGesture()
            .updating($isDetectingTap) { (body, stateType, transaction) in
                // nothing happens below
                print(body)
                print(stateType)
                print(transaction)
            }.onEnded { _ in
                // this one works but it is to late
                // I need to figure out the beginning of the tap
                print("Button was tapped, will invoke haptic feedback, maybe with UIKit")
        }

        return Button(action: {
            print("Action executed")
        }) {
            HStack {
                Image("icon")
                Text("Login")
            }
        }.simultaneousGesture(tap)
    }
}

【问题讨论】:

  • 您可以在ButtonStyle中获取isPressed,查看示例here
  • 对此有明确的解决方案吗? @mrsimply 是否包含 Victor 建议的示例代码?

标签: ios


【解决方案1】:

据我所知,这是在 SwiftUI 中获得触觉反馈的最简单方法

点击按钮时:

Button(action: {
    let impactMed = UIImpactFeedbackGenerator(style: .medium)
    impactMed.impactOccurred()
}) {
    Text("This is a Button")
}

您可以通过将.medium 替换为.soft.light.heavy.rigid 来更改触觉反馈的强度

或在点击其他任何东西时:

.onTapGesture {
    let impactHeavy = UIImpactFeedbackGenerator(style: .heavy)
    impactHeavy.impactOccurred()
}

如果您想制作类似 Haptic Touch 的东西,请将 .onTapGesture 替换为 .onLongPressGesture 像这样

.onLongPressGesture {
    let impactHeavy = UIImpactFeedbackGenerator(style: .heavy)
    impactHeavy.impactOccurred()
}

【讨论】:

  • 样式还有 .soft 和 .rigid 选项
  • 谢谢@Learn2Code,我忘了包括那两个。
  • 谢谢你!这真的很简单。我想要的只是让它在用户点击按钮时获得反馈。完美的工具!
【解决方案2】:

您还可以将 Haptic 生成器封装在一个单例中:

import UIKit

class Haptics {
    static let shared = Haptics()
    
    private init() { }

    func play(_ feedbackStyle: UIImpactFeedbackGenerator.FeedbackStyle) {
        UIImpactFeedbackGenerator(style: feedbackStyle).impactOccurred()
    }
    
    func notify(_ feedbackType: UINotificationFeedbackGenerator.FeedbackType) {
        UINotificationFeedbackGenerator().notificationOccurred(feedbackType)
    }
}

调用点:

// As a button action
Button(action: {
    // some action
    Haptics.shared.play(.heavy)
}, label: {
    Text("Button")
})

// As a tap gesture on a View
SomeView()
.onTapGesture(perform: {
    Haptics.shared.play(.light)
})

// In a function
func someFunc() {
    Haptics.shared.notify(.success)
}

选项:

Haptics.shared.play(.heavy)
Haptics.shared.play(.light)
Haptics.shared.play(.medium)
Haptics.shared.play(.rigid)
Haptics.shared.play(.soft)
                
Haptics.shared.notify(.error)
Haptics.shared.notify(.success)
Haptics.shared.notify(.warning)

【讨论】:

    【解决方案3】:

    下面的纯 SwiftUI 解决方案:

    HStack {
        Image("icon")
        Text("Login")
    }
    .onTouchGesture(
        touchBegan: { self.generateHapticFeedback = true },
        touchEnd: { _ in self.generateHapticFeedback = false }
    )
    

    只需在项目中的某处添加这个 sn-p:

    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))
        }
    }
    

    【讨论】:

    • 试一试,但它不会产生任何触觉反馈。
    【解决方案4】:

    你可以像这样使用UIFeedbackGenerator

    let generator = UINotificationFeedbackGenerator()
    generator.notificationOccurred(.error)
    

    或者,当您使用 SwiftUI 时,您将能够像这样使用CoreHaptics

    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)
    

    【讨论】:

    • 您的回答与我的问题无关。我知道触觉发生器是如何工作的。问题是如何在执行操作之前检测 SwiftUI 中的点击。
    • @mrsimply 如果触觉反馈与您的问题无关,请编辑您的问题并删除提及的触觉反馈。
    • @mrsimply 这应该是公认的答案。在按钮的动作中使用反馈生成器可能是在 SwiftUI 中触发触觉反馈的最简单方法。
    【解决方案5】:

    我有一个播放触觉事件的包装视图,然后调用该动作:

    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()
        }
      }
    }
    

    像这样使用:

    HapticButton("Press me") { print("hello") } // plays haptic and prints
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-07-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多