我在 SwiftUI 中的 macOS 应用程序,定时器在 window (NSWindow) 关闭后永不停止
My macOS app in SwiftUI, the Timer never stop after window (NSWindow) closed
我在视图中使用计时器来显示时间。在View的onAppear()和onDisappear()方法中,Timer效果很好
但是当我关闭 window 时,似乎没有调用 onDisappear() 方法,并且 Timer 永远不会停止。
有我的测试代码:
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)
}
}
那么,window关闭后应该如何正确停止定时器呢?
以及window关闭时如何通知View,旨在释放View实例中的一些资源。
(我想出了一个技巧,使用 TimerPublisher 替换 Timer,它会在 window 关闭后自动停止。但这并不能解决我的困惑。)
使用 .hostingWindow
环境(来自 ),可以使用以下方法。
测试 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)
}
}
哇,我找到了一个更简单明了的解决方案。
在视图结构中,我们可以分配一个 NSWindowDelegate
属性 来监听托管事件 window 并管理应该手动控制的资源对象。
示例:
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
我遇到了类似的问题,我选择了以下方法。
window.isReleasedWhenClosed = false
window.contentView = 无
.onAppear() {
print("onAppear...")
}
.onDisappear() {
print("onDisappear...")
}
.doDisappearFromWillCloseNSWindow(window: window)
extension View {
@ViewBuilder
func doDisappearFromWillCloseNSWindow(window: Any?) -> some View {
#if os(macOS)
let win = window as? NSWindow
let noti = NotificationCenter.default.publisher(for: NSWindow.willCloseNotification, object: win)
self.onReceive(noti) { _ in
print("onReceive... willCloseNotification")
win?.contentView = nil // <--- call onDisappear
}
#else
self
#endif
}
}
我在视图中使用计时器来显示时间。在View的onAppear()和onDisappear()方法中,Timer效果很好
但是当我关闭 window 时,似乎没有调用 onDisappear() 方法,并且 Timer 永远不会停止。
有我的测试代码:
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)
}
}
那么,window关闭后应该如何正确停止定时器呢?
以及window关闭时如何通知View,旨在释放View实例中的一些资源。
(我想出了一个技巧,使用 TimerPublisher 替换 Timer,它会在 window 关闭后自动停止。但这并不能解决我的困惑。)
使用 .hostingWindow
环境(来自
测试 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)
}
}
哇,我找到了一个更简单明了的解决方案。
在视图结构中,我们可以分配一个 NSWindowDelegate
属性 来监听托管事件 window 并管理应该手动控制的资源对象。
示例:
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
我遇到了类似的问题,我选择了以下方法。
window.isReleasedWhenClosed = false
window.contentView = 无
.onAppear() {
print("onAppear...")
}
.onDisappear() {
print("onDisappear...")
}
.doDisappearFromWillCloseNSWindow(window: window)
extension View {
@ViewBuilder
func doDisappearFromWillCloseNSWindow(window: Any?) -> some View {
#if os(macOS)
let win = window as? NSWindow
let noti = NotificationCenter.default.publisher(for: NSWindow.willCloseNotification, object: win)
self.onReceive(noti) { _ in
print("onReceive... willCloseNotification")
win?.contentView = nil // <--- call onDisappear
}
#else
self
#endif
}
}