Как использовать LongPressGesture и объединить с таймером, чтобы «накачать» значения для моделирования? - PullRequest
0 голосов
/ 09 марта 2020

Это для 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»
«Таймер отменен»

Но при втором нажатии я просто получаю:
«Таймер запущен»

Без дальнейших действий

Примечание Мне также пришлось временно переместить ссылку на отменяемую за пределы просмотра, как только я получил предупреждение об изменении представления при обновлении.

Может кто-нибудь выяснить, почему замыкание .onReceive вызывается только один раз? Спасибо!

1 Ответ

0 голосов
/ 09 марта 2020

попробуйте это (копировать - вставить - запустить) 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 прав, что после отмены издателя его снова нельзя использовать Это верно для любого издателя, но не указывает c для Timer.Publisher.

enter image description here

, если вам нравится "publi sh" для некоторых изменения в вашей модели вы можете сделать это стандартным способом

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