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:
ViewModel
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