With the structure in place, let’s start writing the first function:

Displays the current time in real time through timer

To achieve this function, the main idea lies in: through logic, influence UI display effect, which accords with our protagonist today: Combine.

To satisfy use of Combine, two elements are required:

  1. ViewModel
  2. SwiftUI View

ViewModel

Before creating the ViewModel, we use a Package called 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.

Use it mainly because a multilingual version can be made in the future.

Today is mainly to show the Chinese format:

Self.context = Date().toformat ("yyyy yyyy MM dd HH: MM :ss")Copy the code

The next step is to use a timer that updates self.context every second:

Cancellable = timer.publish (every: 1.0, tolerance: nil, on:.main, in:.common, options: Nil).autoconnect().sink {_ in self.context = Date().toformat (" YYYY yyyy MM MM DD HH: MM :ss") print(self.context)}Copy the code

All that remains is to sync the context to the SwiftUI View using Combine.

@Published var context = "fanlymenu"
Copy the code

We create the TimerViewModel to integrate ObservableObject with the following code:

import Foundation import Combine import SwiftDate final class TimerViewModel: ObservableObject {// The notification bar displays the content. As the business evolves, @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 yyyy MM dd HH: MM :ss") print(self.context)}} func stopTimer() { isTimerRunning = false cancellable?.cancel() } func resetTimer() { context = "" } }Copy the code

SwiftUI View

With ViewModel in place, we can call the timerViewModel variable in yesterday’s code:

@ObservedObject private var timerViewModel = TimerViewModel()
Copy the code

Then inject it into the View:

let menuView = ContentView(timerViewModel: timerViewModel)
Copy the code

Specific 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())
    }
}
Copy the code

This injects the ViewModel defined context into the Text SwiftUI View.

Try to use

Back to applicationDidFinishLaunching, we set statusItem length is: 200

statusItem = NSStatusBar.system.statusItem(withLength: CGFloat(200))
Copy the code

Since the Button wrapped in statusItem is not SwiftUI View, I use NSHostingView to convert ContentView from SwiftUI View to NSView:

let view = NSHostingView(rootView: ContentView(timerViewModel: timerViewModel))
Copy the code

Finally, add this child View to the Button:

let view = NSHostingView(rootView: ContentView(timerViewModel: timerViewModel))
view.frame = CGRect(x: 0, y: 0, width: 200, height: 20)
MenuButton.addSubview(view)
Copy the code

Ok, let’s run it and see what it looks like:

The entire code:

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) } } } }Copy the code

conclusion

Use the Combine today to test the date in real time on the menu bar.

The next step is to wrap it into a separate class, and Popover.

To be continued