Мое приложение macOS в SwiftUI, таймер никогда не останавливается после закрытия окна (NSWindow) - PullRequest
2 голосов
/ 03 августа 2020

Я использую таймер в просмотре, чтобы показывать время. В методах onAppear () и onDisappear () View таймер работает хорошо.

Но когда я закрываю окно, кажется, что метод onDisappear () не вызывается, и таймер никогда не останавливается.

Вот мой тестовый код:

import SwiftUI
    
struct TimerTest: View {
    @State var date = Date()
    @State var showSubView = false
    @State var timer: Timer?
    
    var body: some View {
        ZStack{
            if showSubView {
                VStack {
                    Text(" Timer Stoped?")
                    Button("Back") {
                        self.showSubView = false
                    }
                }
            }
            else {
                VStack {
                    Button("Switch to subview"){
                        self.showSubView = true
                    }

                    Text("date: \(date)")
                        .onAppear(perform: {
                            self.timer = Timer.scheduledTimer(withTimeInterval: 1,
                                            repeats: true,
                                            block: {_ in
                                              self.date = Date()
                                              NSLog("?onAppear timer triggered")
                                             })
                        })
                        .onDisappear(perform: {
                            self.timer?.invalidate()
                            self.timer = nil
                            NSLog("? onDisappear stop timer")
                            // But if I close window, this method never be called
                        })
                }
            }
        }
        .frame(width: 500, height: 300)
    }
}
  1. Итак, как мне правильно остановить таймер после закрытия окна?

  2. И как View может быть уведомлен, когда окно будет закрыто, стремитесь освободить некоторые ресурсы в экземпляре View.

(Я придумал хитрый метод, используя TimerPublisher, заменив Timer, который будет автоматически останавливаться после закрытия окна. Но это не решает моей путаницы.)

Ответы [ 2 ]

0 голосов
/ 05 августа 2020

Вау, я нашел более простое и понятное решение.

В структуре View мы могли бы назначить свойство NSWindowDelegate, которое прослушивает событие окна хостинга и управляет объектами ресурсов, которые должны управляться вручную.

Пример:

import SwiftUI
    
struct TimerTest: View {
    @State var date = Date()
    @State var showSubView = false

    // This windowDelegate listens to the window events 
    // and manages resource objects like a Timer.
    var windowDelegate: MyWindowDelegate = MyWindowDelegate()
    
    var body: some View {
        ZStack{
            if showSubView {
                VStack {
                    Text(" Timer Stoped?")
                    Button("Back") {
                        self.showSubView = false
                    }
                }
            }
            else {
                VStack {
                    Button("Switch to subview"){
                        self.showSubView = true
                    }

                    Text("date: \(date)")
                        .onAppear(perform: {
                            self.windowDelegate.timer = Timer.scheduledTimer(withTimeInterval: 1,
                                            repeats: true,
                                            block: {_ in
                                              self.date = Date()
                                              NSLog("? onAppear timer triggered")
                                             })
                        })
                        .onDisappear(perform: {
                            self.windowDelegate.timer?.invalidate()
                            self.windowDelegate.timer = nil
                            NSLog("? onDisappear stop timer")
                        })
                }
            }
        }
        .frame(width: 500, height: 300)
    }
    
    class MyWindowDelegate: NSObject, NSWindowDelegate {
        var timer: Timer?
        
        func windowWillClose(_ notification: Notification) {
            NSLog("? window will close. Stop timer")
            self.timer?.invalidate()
            self.timer = nil
        }
    }
}

А затем в AppDelegate.swift назначьте свойство View.windowDelegate для NSWindow.delegate:

window.contentView = NSHostingView(rootView: contentView)
window.delegate = contentView.windowDelegate
0 голосов
/ 03 августа 2020

При использовании среды .hostingWindow (из Как получить доступ к собственному окну в представлении SwiftUI? ) можно использовать следующий подход.

Протестировано с Xcode 11.4 / iOS 13,4

struct TimerTest: View {
    @Environment(\.hostingWindow) var myWindow
    @State var date = Date()
    @State var showSubView = false
    @State var timer: Timer?

    var body: some View {
        ZStack{
            if showSubView {
                VStack {
                    Text(" Timer Stoped?")
                    Button("Back") {
                        self.showSubView = false
                    }
                }
            }
            else {
                VStack {
                    Button("Switch to subview"){
                        self.showSubView = true
                    }

                    Text("date: \(date)")
                        .onAppear(perform: {
                            self.timer = Timer.scheduledTimer(withTimeInterval: 1,
                                            repeats: true,
                                            block: {_ in
                                              self.date = Date()
                                              NSLog("?onAppear timer triggered")
                                             })
                        })
                        .onDisappear(perform: {
                            self.timer?.invalidate()
                            self.timer = nil
                            NSLog("? onDisappear stop timer")
                            // But if I close window, this method never be called
                        })
                }
                .onReceive(NotificationCenter.default.publisher(for: NSWindow.willCloseNotification, object: myWindow())) { _ in
                    self.timer?.invalidate()
                    self.timer = nil
                }
            }
        }
        .frame(width: 500, height: 300)
    }
}
...