如何使用LongPressGesture并与Timer结合使用以“抽取”值进行建模?

时间:2020-03-09 01:26:59

标签: swiftui combine

这是针对MacOS的。我试图弄清楚如何在用户按住自定义按钮的同时将值泵送到模型中。基本上,我试图重新创建一个MouseDown / MouseUp组合,并在两者之间触发一个计时器。仅使用LongPressGesture似乎不可能,所以我一直在尝试使用Combine和Timer,但仅获得部分成功。我有以下内容:

import SwiftUI
import Combine

var cancellable: Cancellable?

struct ContentView: View {
    var body: some View {
        ZStack{
            FastForwardButton().frame(width: 40, height: 40)
        }.frame(width: 200, height: 200)
    }
}

struct FastForwardButton: View {
    var timer = Timer.publish(every: 0.2, on: .main, in: .common)
    @GestureState private var isPressed = false
   // @State var cancellable: Cancellable?

    var body: some View {
        Rectangle()
            .gesture(
                LongPressGesture(minimumDuration: 4)
                    .updating($isPressed, body: { (currentState, state, transaction) in
                        if self.isPressed == false{
                            state = currentState
                            print("Timer Started")
                            cancellable = self.timer.connect()
                        }
                    })
            )

         .onReceive(timer) { time in
            // Do Something here eg myModel.pump()
            print("The time is \(time)")
            if self.isPressed == false{
                print("Timer Cancelled")
                cancellable?.cancel()
              //  cancellable = nil
            }
        }
    }
}

上面的方法一次。我得到:

“计时器已开始”
“时间是xxx”
.....
“时间是xxx”
“计时器已取消”

第二次按下我就得到了:
“计时器已开始”

没有进一步的动作

请注意,当我收到有关在更新时修改View的警告时,我还不得不将引用暂时移到View之外的cancellable。

谁能弄清楚为什么.onReceive关闭仅被调用一次?谢谢!

1 个答案:

答案 0 :(得分:0)

尝试此(复制-粘贴-运行)macOS

import SwiftUI
import Combine

class Model: ObservableObject {
    var timerPublisher: Timer.TimerPublisher?
    var handle: AnyCancellable?
    var startstop: Cancellable?

    func start() {
        timerPublisher = Timer.publish(every: 0.5, on: RunLoop.main, in: .default)
        handle = timerPublisher?.sink(receiveValue: {
            print($0)
        })
        startstop = timerPublisher?.connect()
        print("start")
    }
    func stop() {
        startstop?.cancel()
        print("stop")
    }
}

struct ContentView: View {
    @ObservedObject var model = Model()
    var body: some View {
        VStack {
            Button(action: {
                self.model.start()
            }) {
                Text("Start")
            }
            Button(action: {
                self.model.stop()
            }) {
                Text("Stop")
            }
        }
            .frame(maxWidth: .infinity, maxHeight: .infinity)
    }
}


struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

它显示了两个按钮,您可以开始停止发布...(Apsperi正确地是,一旦取消发布者,它就无法再次使用。这对于任何发布者都是如此,而不是特定于Timer.Publisher。

enter image description here

如果您想“发布”模型中的某些更改,则可以采用标准方式进行

func start() {
    timerPublisher = Timer.publish(every: 0.5, on: RunLoop.main, in: .default)
    handle = timerPublisher?.sink(receiveValue: {
        print($0)
        self.objectWillChange.send()
    })
    startstop = timerPublisher?.connect()
    print("start")
}

问题是为什么不简单使用.autoconnect()?答案是灵活性,请考虑一下此模型

class Model: ObservableObject {
    var timerPublisher: Timer.TimerPublisher?
    var handle: AnyCancellable?
    var startstop: Cancellable?
    func createTimerPublisher() {
        timerPublisher = Timer.publish(every: 0.5, on: RunLoop.main, in: .default)
        handle = timerPublisher?.sink(receiveValue: {
            print($0)
            self.objectWillChange.send()
        })
    }
    init() {
        createTimerPublisher()
    }
    func start() {
        startstop = timerPublisher?.connect()
        print("start")
    }
    func stop() {
        startstop?.cancel()
        print("stop")
        // or create it conditionaly for later use
        createTimerPublisher()
    }
}

更新,其中在某些视图上上下移动鼠标可启动/停止计时器

import SwiftUI
import Combine

class Model: ObservableObject {
    var timerPublisher: Timer.TimerPublisher?
    var handle: AnyCancellable?
    var startstop: Cancellable?
    func createTimerPublisher() {
        timerPublisher = Timer.publish(every: 0.5, on: RunLoop.main, in: .default)
        handle = timerPublisher?.sink(receiveValue: {
            print($0)
            self.objectWillChange.send()
        })
    }
    init() {
        createTimerPublisher()
    }
    func start() {
        // if
        startstop = timerPublisher?.connect()
        print("start")
    }
    func stop() {
        startstop?.cancel()
        startstop = nil
        print("stop")
        // or create it conditionaly for later use
        createTimerPublisher()
    }
}

struct ContentView: View {
    @ObservedObject var model = Model()
    var body: some View {
        VStack {
            Button(action: {
                self.model.start()
            }) {
                Text("Start")
            }
            Button(action: {
                self.model.stop()
            }) {
                Text("Stop")
            }
            MouseUpDownRepresentable(content: Rectangle()).environmentObject(model)
            .frame(width: 50, height: 50)
        }
            .frame(maxWidth: .infinity, maxHeight: .infinity)
    }
}

class MouseUpDownViewClass<Content>: NSHostingView<Content> where Content : View {
    let model: Model
    required init(model: Model, rootView: Content) {
        self.model = model
        super.init(rootView: rootView)
    }

    @objc required dynamic init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    required init(rootView: Content) {
        fatalError("init(rootView:) has not been implemented")
    }

    override func mouseUp(with event: NSEvent) {
        print("mouse up")
        model.stop()
    }
    override func mouseDown(with event: NSEvent) {
        print("mouse down")
        model.start()
    }
}

struct MouseUpDownRepresentable<Content>: NSViewRepresentable where Content: View {
    @EnvironmentObject var model: Model
    let content: Content

    func makeNSView(context: Context) -> NSHostingView<Content> {
        return MouseUpDownViewClass(model: model, rootView: self.content)
    }

    func updateNSView(_ nsView: NSHostingView<Content>, context: Context) {
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

enter image description here

如果它看起来太复杂,则可以仅用SwiftUI手势创建它。技巧使用了不同的手势识别

struct TouchView: View {
    @State var pressed = false
    var body: some View {
        Circle().fill(pressed ? Color.yellow : Color.orange)
            .gesture(
                DragGesture(minimumDistance: 0)
                    .onChanged({ (touch) in
                        if self.pressed == false {
                            self.pressed = true
                            print("start")
                        }
                    })
                    .onEnded({ (touch) in
                        print("stop")
                        self.pressed = false
                    })
            )
    }
}