如何使LongPressGesture在SwiftUI中仍然保持按下状态时反复运行?

时间:2020-06-07 01:21:28

标签: swift swiftui uigesturerecognizer

我想在按住按钮的同时每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
            }
        }
    }
}

3 个答案:

答案 0 :(得分:2)

哦,我不是一个真正的专家,但是最近我遇到了类似的问题(检测按下和释放),而我发现的解决方案还不够优雅。我希望有人能提供更优雅的解决方案,但这是我的怪癖:

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的工作原理-您可以漫步并返回,并且只要将按钮的顶部抬起即可视图中,该手势将被识别为按下按钮。我们也应该在长按中复制这种行为,因此 maximumDistance:.infinity ;
  • 使用这些参数,LongPressGesture将永远不会被识别,但是有一个 press 参数现在可以让我们在按下按键开始和结束时得到通知;
  • 某种计时器可能经常被用来执行代码块。我已经从某个地方复制了此“ ticker” ObservableObject。它必须是一个ObservableObject,因为这样我们就可以在View中订阅它的更新;
  • 现在,当按下按钮时,我们启动置顶器;
  • 股票行情线滴答作答时,我们使用 onReceive()订阅者捕获了该行情,这使我们可以在每个行情滴答处做些事情。

类似的东西;再一次,我希望有人给我展示一种更好的方法:)

祝您项目顺利!

–Baglan

答案 1 :(得分:2)

您可以使用计时器来做到这一点。让计时器在用户长按图像时启动,如果计时器达到0,您可以添加两个操作:1.将计时器重置为0.5秒,以及2.您希望每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

                       }
                       )
               }
}
}

答案 2 :(得分:0)

今天早上我只是清理了@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
    }
}