通向荣誉的路上,并不铺满鲜花。前端
用Java爬到我房间电表电量使用状况后,封装了一个接口,用于客户端的调用😝 在平常生活中,我用Mac的时间是最多的,若是将爬到的数据,展现在Mac的顶栏上,是一件很美好的事情😌 做为一个对Swift一无所知的我,花了两天时间,用Swift语言开发了一个Mac应用,接下来就跟你们分享下这个应用的开发过程,欢迎各为感兴趣的开发者阅读本文。git
先跟你们看下最终实现的效果: github
Swift: 4.2.1npm
Xcode: 10.1json
MacOS: 10.14.2swift
库管理工具: Carthageapi
Alamofire: 4.0 (网络请求库)xcode
SwiftyJSON: 3.0 (Json解析库)bash
此时项目为一个空白项目,点运行后会出现一个空的window窗口,同时dock上出现应用图标,这并非咱们要的,因此要添加配置来移除他们。 网络
// 建立状态栏按钮
let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength)
复制代码
// applicationDidFinishLaunching生命周期
if let button = statusItem.button {
button.image = NSImage(named: "StatusIcon")
}
复制代码
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
}
}
复制代码
// 声明一个Popover
let popover = NSPopover()
复制代码
// 控制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)
}
复制代码
if let button = statusItem.button {
button.image = NSImage(named: "StatusIcon")
button.action = #selector(togglePopover(_:))
}
popover.contentViewController = PopoverViewController.freshController()
复制代码
执行完上个步骤后,咱们会发现弹层只会在点击时关闭或者消失,接下来咱们来优化下,失去焦点时,也让它隐藏
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
}
}
}
复制代码
// 声明监视器
var eventMonitor: EventMonitor?
复制代码
eventMonitor = EventMonitor(mask: [.leftMouseDown, .rightMouseDown]) { [weak self] event in
if let strongSelf = self, strongSelf.popover.isShown {
strongSelf.closePopover(event!)
}
}
复制代码
// 显示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()
}
复制代码
// 接管togglePopover
@objc func mouseClickHandler() {
if let event = NSApp.currentEvent {
switch event.type {
case .leftMouseUp:
togglePopover(popover)
default:
statusItem.menu = Menu
statusItem.button?.performClick(nil)
}
}
}
复制代码
// 点击事件
button.action = #selector(mouseClickHandler)
button.sendAction(on: [.leftMouseUp, .rightMouseUp])
复制代码
extension AppDelegate: NSMenuDelegate {
// 为了保证按钮的单击事件设置有效,menu要去除
func menuDidClose(_ menu: NSMenu) {
self.statusItem.menu = nil
}
}
复制代码
// 修复按钮单击事件无效问题
Menu.delegate = self
复制代码
// 关闭App
@IBAction func Quit(_ sender: Any) {
NSApplication.shared.terminate(self)
}
复制代码
执行完上述步骤后,咱们建立了一个空的Popover,接下来咱们往Popover添加内容,调用接口,显示咱们房间电表电量使用状况。
Cartfile是一个优秀的库管理工具,至关于咱们前端的npm。
Alamofire,为一个优秀的网络请求库,他封装了各类http请求。
SwiftyJSON,为一个优秀的json解析库
咱们能够经过Cartfile来获取他们
github "Alamofire/Alamofire" ~> 4.0
github "SwiftyJSON/SwiftyJSON" ~> 3.0
复制代码
carthage update --platform macOS
复制代码
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爬取电表电量使用状况
本篇文章对应的代码地址: home-battery-tool