Swift реализует разработку приложения строки меню MacOS

Swift
Swift реализует разработку приложения строки меню MacOS

Дорога к чести не усыпана цветами.

предисловие

После того, как с помощью Java забрался к потреблению электроэнергии электросчетчиком в моей комнате, я инкапсулировал интерфейс для клиентских вызовов 😝 В своей повседневной жизни я использую Mac чаще всего, было бы замечательно, если бы данные, к которым я залез, отображались на верхней панели Mac 😌 Как человек, который ничего не знает о Swift, я потратил два дня на разработку приложения для Mac на языке Swift.Далее я поделюсь с вами процессом разработки этого приложения.Все заинтересованные разработчики могут прочитать эту статью.

Давайте посмотрим на окончательный результат:

Строительство окружающей среды

Swift: 4.2.1

Xcode: 10.1

MacOS: 10.14.2

Инструмент управления библиотекой: Carthage

Alamofire: 4.0 (библиотека сетевых запросов)

SwiftyJSON: 3.0 (библиотека разбора Json)

Создать проект

  • Открываем Xcode, видим интерфейс как показано на рисунке
    • Слева раздел «Созданный проект», справа недавно открытый проект
    • Нажмите на место, отмеченное красным на картинке
  • Как показано на рисунке, нажмите на серийные номера, указанные на рисунке.
  • Как показано на рисунке, заполните соответствующую информацию о проекте соответственно.
  • Выберите, где создать проект
  • После появления страницы, показанной на рисунке, проект успешно создан.

Настройте элемент как приложение строки меню

В настоящее время проект является пустым проектом. После нажатия кнопки «Выполнить» появится пустое окно, а на доке появится значок приложения. Это не то, что нам нужно, поэтому нам нужно добавить конфигурацию, чтобы удалить их.

  • Как показано на рисунке, добавьте элемент конфигурации под информацией
  • Выбор ключаApplication is agent (UI Element),Значение выбрать Да
  • В этот момент снова запустим программу, и мы обнаружим, что значок программы больше не отображается в док-баре, но окно окна все еще там.
  • Фигура показана, удаленаWindow Controller Sceneа такжеView Controller Scene
  • В это время мы находимся в приложении облачного сердца и обнаружили, что окно исчезло.

Создать значок в строке меню

  • Как показано на рисунке, нажмитеAssets.xcassetsфайл, создайте новыйImage Set
  • Как показано, назовите его какStatusIcon, сделайте свою любимую картинку в 3 вида спецификаций и поместите ее в соответствующую позицию.
  • После перетаскивания значка следуйте инструкциям на картинке, чтобы значок адаптировался к темному режиму системы.
  • Откройте файл AppDelegate.swift и добавьте следующий код.
    // 创建状态栏按钮
    let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength)
    // applicationDidFinishLaunching生命周期
    if let button = statusItem.button {
         button.image = NSImage(named: "StatusIcon")
    }
  • Запустив программу, мы обнаружили, что в строке меню есть значок, который мы только что установили, и после нажатия на него ничего не происходит.

Добавить кPopoverконтейнер

  • В каталоге проекта создайте новыйCocoa Class, по имениPopoverViewController, этот файл представляет собой всплывающую страницу, которая открывается при нажатии
  • Добавить контейнер View Controller
  • Как показано на рисунке, измените контейнер, добавленный на предыдущем шаге.
  • Откройте файл PopoverViewController.swift и добавьте в конец следующий код.
extension PopoverViewController {
    static func freshController() -> PopoverViewController {
        //获取对Main.storyboard的引用
        let storyboard = NSStoryboard(name: NSStoryboard.Name("Main"), bundle: nil)
        // 为PopoverViewController创建一个标识符
        let identifier = NSStoryboard.SceneIdentifier("PopoverViewController")
        // 实例化PopoverViewController并返回
        guard let viewcontroller = storyboard.instantiateController(withIdentifier: identifier) as? PopoverViewController else {
            fatalError("Something Wrong with Main.storyboard")
        }
        return viewcontroller
    }
}

  • Откройте файл AppDelegate.swift и добавьте в класс следующий код.
// 声明一个Popover
let popover = NSPopover()

Создайте функцию для отображения/скрытия всплывающего окна.

  • Добавьте следующий код в класс файла AppDelegate.swift.
    // 控制Popover状态
    @objc func togglePopover(_ sender: AnyObject) {
        if popover.isShown {
            closePopover(sender)
        } else {
            showPopover(sender)
        }
    }
    // 显示Popover
    @objc func showPopover(_ sender: AnyObject) {
        if let button = statusItem.button {
            popover.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY)
        }
        
    }
    // 隐藏Popover
    @objc func closePopover(_ sender: AnyObject) {
        popover.performClose(sender)
    }
  • в файле AppDelegate.swiftapplicationDidFinishLaunchingДобавьте следующий код в функцию
    if let button = statusItem.button {
            button.image = NSImage(named: "StatusIcon")
            button.action = #selector(togglePopover(_:))
    }
    popover.contentViewController =            PopoverViewController.freshController()
  • В этот момент запустите проект, щелкните значок приложения в строке меню, отобразится всплывающий слой, и снова щелкните всплывающий слой, он исчезнет.

Оптимизировать всплывающее окно

После выполнения предыдущего шага мы обнаружим, что всплывающий слой будет закрываться или исчезать только при нажатии.Далее давайте оптимизируем его и скроем при потере фокуса.

  • Создайте новый файл EventMonitor.swift для мониторинга событий, код этого файла выглядит следующим образом
import Cocoa

public class EventMonitor {
    private var monitor: Any?
    private let mask: NSEvent.EventTypeMask
    private let handler: (NSEvent?) -> Void
    
    public init(mask: NSEvent.EventTypeMask, handler: @escaping (NSEvent?) -> Void) {
        self.mask = mask
        self.handler = handler
    }
    
    deinit {
        stop()
    }
    
    public func start() { //开启监视器
        monitor = NSEvent.addGlobalMonitorForEvents(matching: mask, handler: handler)
    }
    
    public func stop() { //关闭监视器
        if monitor != nil {
            NSEvent.removeMonitor(monitor!)
            monitor = nil
        }
    }
}

  • Откройте AppDelegate.swift и объявите этот монитор в классе
// 声明监视器
var eventMonitor: EventMonitor?
  • Добавить функцию applicationdiDfinishlaumping
eventMonitor = EventMonitor(mask: [.leftMouseDown, .rightMouseDown]) { [weak self] event in
  if let strongSelf = self, strongSelf.popover.isShown {
    strongSelf.closePopover(event!)
  }
}
  • Измените функции showPopover и closePopover.
    // 显示Popover
    @objc func showPopover(_ sender: AnyObject) {
        if let button = statusItem.button {
            popover.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY)
        }
        eventMonitor?.start()
        
    }
    // 隐藏Popover
    @objc func closePopover(_ sender: AnyObject) {
        popover.performClose(sender)
        eventMonitor?.stop()
    }
  • Запустив проект, мы обнаружили, что слой маркера был скрыт после потери фокуса.

Добавить контекстное меню

  • Как показано на рисунке, добавьте компонент меню, чтобы войти
  • Связать меню с AppDelegate.swift
  • Удалите лишние элементы, добавьте значки выхода и записи
  • Измените файл AppDelegate.swift и добавьте обработчик, чтобы взять на себя функцию togglePopover.
// 接管togglePopover
    @objc func mouseClickHandler() {
        if let event = NSApp.currentEvent {
            switch event.type {
            case .leftMouseUp:
                togglePopover(popover)
            default:
                statusItem.menu = Menu
                statusItem.button?.performClick(nil)
            }
        }
    }
  • statusItem.button добавьте следующий код
// 点击事件
 button.action = #selector(mouseClickHandler)
 button.sendAction(on: [.leftMouseUp, .rightMouseUp])
  • Добавить в конец AppDelegate.swift
extension AppDelegate: NSMenuDelegate {
    // 为了保证按钮的单击事件设置有效,menu要去除
    func menuDidClose(_ menu: NSMenu) {
        self.statusItem.menu = nil
    }
}

  • Добавить внутри приложенияDidFinishLaunching
// 修复按钮单击事件无效问题
Menu.delegate = self
  • В этот момент кликаем правой кнопкой мыши, обнаруживаем, что он успешно добавлен

Реализовать функцию выхода

  • Как показано на рисунке, свяжите функцию push с файлом AppDelegate.
  • Улучшить функцию выхода из приложения
    // 关闭App
    @IBAction func Quit(_ sender: Any) {
        NSApplication.shared.terminate(self)
    }
  • После повторного запуска щелкните правой кнопкой мыши и выберите «Выход», чтобы закрыть приложение.

Написание всплывающих страниц

После выполнения вышеперечисленных шагов мы создали пустой Popover, а затем добавили в него содержимое, вызвали интерфейс и отобразили потребление электроэнергии счетчиком электроэнергии в нашей комнате.

страница макета

  • Как показано на рисунке, добавьте компоненты Text Field и Label, перетащите их в PopoverViewController и свяжите с PopoverViewController.swift.
  • Отрегулируйте размер перетаскиваемого элемента управления, чтобы он выглядел как на картинке.

Установите инструмент управления библиотекой Cartfile.

CartfileЭто отличный инструмент управления библиотекой, эквивалентный нашему внешнему интерфейсу npm.

  • нажмитеадрес загрузки корзины, перейдите на страницу загрузки репозитория Cartfile на github, выберите файл pkg для загрузки, а затем установите его.

Установите библиотеку сетевых запросов и библиотеку разбора Json.

Alamofire, превосходная библиотека сетевых запросов, инкапсулирует различные HTTP-запросы.

SwiftyJSON, отличная библиотека для разбора json.

Мы можем получить их через Cartfile

  • Создайте файл Cartfile в корневом каталоге нашего проекта и добавьте следующее содержимое
github "Alamofire/Alamofire" ~> 4.0
github "SwiftyJSON/SwiftyJSON" ~> 3.0
  • Откройте терминал, войдите в корневой каталог нашего проекта и выполните следующую команду
carthage update --platform macOS
  • После выполнения мы обнаружили, что в корневом каталоге проекта есть еще папки Carthage
  • В этот момент открываем, xcode, открываем страницу как показано на рисунке
  • Как показано на рисунке, выберите файл build->Mac->framework в только что созданной папке Carthage.
  • После успешного добавления, как показано на рисунке

Включить доступ к сети

Xcode не разрешает HTTP-запросы по умолчанию, просто следуйте операциям, показанным на рисунке.

Вызов интерфейса для отображения страницы

Добавьте следующий код в файл PopoverViewController.swift.

import Cocoa
import Alamofire
import SwiftyJSON

class PopoverViewController: NSViewController {
    // 今日用电
    @IBOutlet weak var electricityToday: NSTextField!
    // 本月已用
    @IBOutlet weak var currentMonthBatteryTotal: NSTextField!
    // 剩余电量
    @IBOutlet weak var remainingBattery: NSTextField!
    // 统计时间
    @IBOutlet weak var time: NSTextField!

    private var timer: Timer?
    // 定时器记数: 每20分钟执行一次,3轮为1小时
    private var timeCount = 3
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // 获取并设置页面数据
        setPageData()
        // 启动定时器
        loop()
    }
    
    // 获取并设置数据
    func setPageData(){
        // 发起post请求
        Alamofire.request("https://www.xxx.com",method: .post,parameters: ["userName":"xxx","password":"xxx"],encoding: JSONEncoding.default).responseJSON { (response) in
            switch response.result {
            // 请求成功
            case .success(let resData):
                // 将返回的数据转为JSON对象
                let jsonData = JSON.init(resData as Any)
                // 变量赋值
                self.electricityToday.stringValue = jsonData["data"]["electricityToday"].string!
                self.currentMonthBatteryTotal.stringValue = jsonData["data"]["currentMonthBatteryTotal"].string!
                self.remainingBattery.stringValue = jsonData["data"]["remainingBattery"].string!
                self.time.stringValue = jsonData["data"]["time"].string!
                break
            case .failure(let error):
                print("接口调用失败")
                print(error);
                break
            }
        }
    }
    
    // GCD 方式的定时器,循环
    func loop() {
        print("\(Date()): 定时器初始化")
        // timeInterval: 隔多少秒执行一次
        timer = Timer(timeInterval: 1200, repeats: true, block: { timer in
            self.loopFireHandler(timer)
        })
        // 添加定时器
        RunLoop.main.add(timer!, forMode: .common)
    }
    // 定时器需要执行的内容
    @objc private func loopFireHandler(_ timer: Timer?) -> Void {
        // 定时器执行结束结束
        if self.timeCount <= 0 {
            print("\(Date()): 执行完1轮,开始下一轮")
            self.timeCount = 3
            return
        }
        // 获取并设置页面数据
        setPageData()
        // 执行完分钟
        self.timeCount -= 1
      
    }

}


extension PopoverViewController {
    static func freshController() -> PopoverViewController {
        //获取对Main.storyboard的引用
        let storyboard = NSStoryboard(name: NSStoryboard.Name("Main"), bundle: nil)
        // 为PopoverViewController创建一个标识符
        let identifier = NSStoryboard.SceneIdentifier("PopoverViewController")
        // 实例化PopoverViewController并返回
        guard let viewcontroller = storyboard.instantiateController(withIdentifier: identifier) as? PopoverViewController else {
            fatalError("Something Wrong with Main.storyboard")
        }
        return viewcontroller
    }
}


напиши в конце

Как просканировать использование счетчика электроэнергии в вашей комнате, перейдите в эту статью:Java сканирует потребление электроэнергии электросчетчиком

использованная литература:Что такое стерео.top/2018/06/29/…

Кодовый адрес, соответствующий этой статье:home-battery-tool

  • Если в статье есть ошибки, исправьте их в комментариях, если статья вам поможет, ставьте лайк и подписывайтесь 😊
  • Эта статья была впервые опубликована на Наггетс, перепечатка без разрешения запрещена 💌