阅读 106

SwiftUI版通知栏应用开发(2) ——Combine 小试牛刀创建定时器

搭好结构后,我们开始写第一个功能了:

通过定时器实时显示当前时间

要实现这个功能,主要的思想在于:通过逻辑,影响 UI 的显示效果,这个符合我们今天的主角:Combine

如果要满足使用 Combine,需要具备两要素:

  1. ViewModel

  2. SwiftUI View

ViewModel

在创建 ViewModel 之前,我们先使用一个 Package,那就是 SwiftDate

SwiftDate is the definitive toolchain to manipulate and display dates and time zones on all Apple platform and even on Linux and Swift Server Side frameworks like Vapor or Kitura.

使用它,主要是因为未来可以做一个多语言版本。

今天主要是展示中文格式:

self.context = Date().toFormat("yyyy年MM月dd日 HH:mm:ss")复制代码

下一步是使用一个定时器,每隔一秒更新一次self.context

cancellable = Timer.publish(every: 1.0, tolerance: nil, on: .main, in: .common, options: nil)
    .autoconnect()
    .sink { _ in
        self.context = Date().toFormat("yyyy年MM月dd日 HH:mm:ss")
        print(self.context)
    }复制代码

剩下的就是把 context 利用 Combine 同步到 SwiftUI View 上。

@Published var context = "fanlymenu"复制代码

我们创建 TimerViewModel 集成 ObservableObject,整个代码如下:

import Foundation
import Combine
import SwiftDate

final class TimerViewModel: ObservableObject {
    // 通知栏显示内容,随着业务的发展,不断丰富
    // 这一阶段实现动态时间显示
    @Published var context = "fanlymenu"
    @Published var isTimerRunning = false
    
    private var cancellable: AnyCancellable?
    
    func startTimer() {
        isTimerRunning = true
        cancellable = Timer.publish(every: 1.0, tolerance: nil, on: .main, in: .common, options: nil)
            .autoconnect()
            .sink { _ in
                self.context = Date().toFormat("yyyy年MM月dd日 HH:mm:ss")
                print(self.context)
            }
    }
    
    func stopTimer() {
        isTimerRunning = false
        cancellable?.cancel()
    }
    
    func resetTimer() {
        context = ""
    }
}复制代码

SwiftUI View

有了 ViewModel,我们就可以在昨天的代码里调用了,先定义 timerViewModel 变量:

@ObservedObject private var timerViewModel = TimerViewModel()复制代码

然后注入到 View 中:

let menuView = ContentView(timerViewModel: timerViewModel)复制代码

具体 ContentView

import SwiftUI

struct ContentView: View {
    @ObservedObject private var timerViewModel: TimerViewModel
    
    init(timerViewModel: TimerViewModel) {
        self.timerViewModel = timerViewModel
    }
    
    var body: some View {
        Text("\(self.timerViewModel.context)")
            .padding()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView(timerViewModel: TimerViewModel())
    }
}复制代码

这样就把 ViewModel 定义的 context 注入到 Text SwiftUI View 里了。

尝试使用

回到 applicationDidFinishLaunching,我们把 statusItem 长度设为:200

statusItem = NSStatusBar.system.statusItem(withLength: CGFloat(200))复制代码

因为在 statusItem 包裹的 Button 不是 SwiftUI View,所以我利用 NSHostingViewContentViewSwiftUI View 转为 NSView

let view = NSHostingView(rootView: ContentView(timerViewModel: timerViewModel))复制代码

最后在 Button 加上这个子 View

let view = NSHostingView(rootView: ContentView(timerViewModel: timerViewModel))
view.frame = CGRect(x: 0, y: 0, width: 200, height: 20)
MenuButton.addSubview(view)复制代码

好了,我们运行看看效果:

times

整个代码:

class AppDelegate: NSObject, NSApplicationDelegate {
    
    // Status Bar Item...
    var statusItem: NSStatusItem?
    // PopOver...
    var popOver = NSPopover()
    
    @ObservedObject private var timerViewModel = TimerViewModel()
    
    func applicationDidFinishLaunching(_ notification: Notification) {
//        let timerViewModel = TimerViewModel()
        // Menu View...
        let menuView = ContentView(timerViewModel: timerViewModel)

        // Creating PopOver...
        popOver.behavior = .transient
        popOver.animates = true
        
        // Setting Empty View Controller...
        // And Setting View as SwiftUI View...
        // with the help of Hosting Controller...
        popOver.contentViewController = NSViewController()
        popOver.contentViewController?.view = NSHostingView(rootView: menuView)
        
        // also Making View as Main View...
        popOver.contentViewController?.view.window?.makeKey()
        
        // Creating Status Bar Button...
        statusItem = NSStatusBar.system.statusItem(withLength: CGFloat(200))
        // Safe Check if status Button is Available or not...
        if let MenuButton = statusItem?.button {
//            MenuButton.image = NSImage(systemSymbolName: "icloud.and.arrow.up.fill", accessibilityDescription: nil)
//            MenuButton.imagePosition = NSControl.ImagePosition.imageLeft
//            MenuButton.title = ""
            let view = NSHostingView(rootView: ContentView(timerViewModel: timerViewModel))
            view.frame = CGRect(x: 0, y: 0, width: 200, height: 20)
            MenuButton.addSubview(view)
            
            MenuButton.action = #selector(MenuButtonToggle)
        }
        
        timerViewModel.startTimer()
    }
    
    // Button Action
    @objc func MenuButtonToggle(sender: AnyObject) {
        
        // For Safer Sice...
        if popOver.isShown {
            popOver.performClose(sender)
        } else {
            // Showing PopOver
            if let menuButton = statusItem?.button {
                
                // Top Get Button Location For Popover Arrow...
                self.popOver.show(relativeTo: menuButton.bounds, of: menuButton, preferredEdge: NSRectEdge.maxY)
            }
        }
    }
}复制代码

总结

今天使用 Combine 小试牛刀,把日期实时显示在菜单栏上。

下一步就是封装成单独的类了,以及 Popover

未完待续


作者:叶梅树
链接:https://juejin.cn/post/7023746494553391111


文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐