如何制作在 SwiftUI 中仍按住按钮时重复运行的 LongPressGesture?

How to make a LongPressGesture that runs repeatedly while the button is still being held down in SwiftUI?

我想在按住按钮时每 0.5 秒 运行 longPressGesture 中的代码。关于如何实现这个的任何想法?

import SwiftUI

struct ViewName: View {
    var body: some View {
        VStack {
            Button(action: { } ) {
            Image(systemName: "chevron.left")
            .onTapGesture {
                //Run code for tap gesture here
            }
            .onLongPressGesture (minimumDuration: 0.5) {
                //Run this code every 0.5 seconds
            }
        }
    }
}

哦,天哪,我不是真正的专家,但我最近遇到了类似的问题(检测按下和释放),我找到的解决方案不够优雅。如果有人展示更优雅的解决方案,我会很高兴,但这是我的怪物:

import SwiftUI
import Combine

struct ContentView: View {

    @State private var ticker = Ticker()
    @State private var isPressed: Bool = false
    @State private var timePassed: TimeInterval?

    var body: some View {

        Button(action: {
            // Action when tapped
            NSLog("Tapped!")
        }) {
            Text(self.isPressed ? "Pressed for: \(String(format: "%0.1f", timePassed ?? 0))" : "Press and hold")
                .padding()
                .background(Capsule().fill(Color.yellow))
        }
        .onLongPressGesture(minimumDuration: .infinity, maximumDistance: .infinity, pressing: { (value) in
                self.isPressed = value
                if value == true {
                    self.timePassed = 0
                    self.ticker.start(interval: 0.5)
                }

            }, perform: {})
            .onReceive(ticker.objectWillChange) { (_) in
                // Stop timer and reset the start date if the button in not pressed
                guard self.isPressed else {
                    self.ticker.stop()
                    return
                }

                // Your code here:
                self.timePassed = self.ticker.timeIntervalSinceStarted
            }
    }
}


/// Helper "ticker" that will publish regular "objectWillChange" messages
class Ticker: ObservableObject {

    var startedAt: Date = Date()

    var timeIntervalSinceStarted: TimeInterval {
        return Date().timeIntervalSince(startedAt)
    }

    private var timer: Timer?
    func start(interval: TimeInterval) {
        stop()
        startedAt = Date()
        timer = Timer.scheduledTimer(withTimeInterval: interval, repeats: true) { _ in
            self.objectWillChange.send()
        }
    }

    func stop() {
        timer?.invalidate()
    }

    deinit {
        timer?.invalidate()
    }

}

这需要解释:

  • onTapGesture() 在这里不是必需的,因为这是 Button 默认情况下所做的,所以只需将 运行 所需的代码放在 action 块中就足够了;
  • SwiftUI 中可用的手势数量有限,据我所知,制作新手势的唯一方法是组合现有手势;
  • 没有一种手势是只要按下按钮就会一直执行代码,但LongPressGesture可能是最接近它的。但是,当分配的时间到期时,此手势会被识别(并结束),但您希望检测触摸持续时间,因此 minimumDuration: .infinity 参数;
  • LongPressGesture 也会在触摸移开足够长的距离时结束,但是,这不是 Button 的工作方式 - 你可以走开然后 return 回来,只要你抬起触摸按钮视图的顶部,该手势将被识别为按下按钮。我们也应该在长按中复制这种行为,因此 maximumDistance: .infinity;
  • 使用这些参数,LongPressGesture 将永远不会被识别,但是有一个 press 参数现在允许我们在按下开始和结束时收到通知;
  • 可以使用某种计时器来每隔一段时间执行一个代码块;我从某处复制了这个 "ticker" ObservableObject。它必须是一个 ObservableObject,因为那样的话,我们可以在视图中订阅它的更新;
  • 现在,当按下按钮时,我们启动自动收报机;
  • 当股票报价时,我们使用 onReceive() 订阅者捕获它,这允许我们在每次报价时做一些事情。

类似的东西;再一次,我希望有人能告诉我更好的方法:)

祝你项目顺利!

–巴格兰

您可以使用计时器来完成此操作。使计时器在用户长按图像时启动,如果计时器达到 0,则可以添加两个操作:1. 将计时器重置回 0.5 秒,2.code 你想每 0.5 运行秒

    struct ContentView: View {
    @State var timeRemaining = 0.5
    let timer = Timer.publish(every: 0.5, on: .main, in: .common).autoconnect()
    @State var userIsPressing = false //detecting whether user is long pressing the screen

    var body: some View {
        VStack {
           Image(systemName: "chevron.left").onReceive(self.timer) { _ in
               if self.userIsPressing == true {
                 if self.timeRemaining > 0 {
                    self.timeRemaining -= 0.5
                  }
                //resetting the timer every 0.5 secdonds and executing code whenever //timer reaches 0

         if self.timeRemaining == 0 {
                print("execute this code")
                self.timeRemaining = 0.5
             }
            }
        }.gesture(LongPressGesture(minimumDuration: 0.5)
                       .onChanged() { _ in
                           //when longpressGesture started
                       self.userIsPressing = true
                       }
                       .onEnded() { _ in
                           //when longpressGesture ended
                       self.userIsPressing = false

                       }
                       )
               }
}
}

今天早上我简单地清理了一下@Baglan 的“怪物”。

import Foundation
import SwiftUI

struct LongPressButton: View {
    @ObservedObject var timer = PressTimer()

    enum PressState {
        case inactive
        case pressing
        case finished
    }

    @State private var pressState = PressState.inactive

    var duration: Double = 2.0

    var body: some View {

        button
                .onLongPressGesture(minimumDuration: duration, maximumDistance: 50, pressing: { (value) in
                    if value == true {
                        /// Press has started
                        self.pressState = .pressing
                        print("start")
                        self.timer.start(duration)
                    } else {
                        /// Press has cancelled
                        self.pressState = .inactive
                        print("stop")
                        self.timer.stop()
                    }
                }, perform: {
                    /// Press has completed successfully
                    self.pressState = .finished
                    print("done")
                })
    }

    var button: some View {
        pressState == .pressing ? Text("Pressing - \(String(format: "%.0f", timer.percent))%")
                : Text("Start")
    }
}

class PressTimer: ObservableObject {

    @Published var percent: CGFloat = 0

    private var count: CGFloat = 0
    private let frameRateHz: CGFloat = 60
    private var durationSeconds: CGFloat = 2

    var timer: Timer?

    func start(_ duration: Double = 2.0) {
        self.durationSeconds = CGFloat(duration)
        let timerInterval: CGFloat = 1 / frameRateHz

        timer = Timer.scheduledTimer(withTimeInterval: Double(timerInterval), repeats: true, block: { _ in
            self.count += timerInterval
            self.percent = self.count / self.durationSeconds * 100
        })
    }

    func stop() {
        self.count = 0
        self.percent = 0
        self.timer?.invalidate()
        self.timer = nil
    }
}