• Apple发布官方SwiftUI 制作macOS App教程 在创建了适用于watchOS的Landmarks应用程序的版本之后,是时候将您的目光投向更大的东西了:将Landmarks引入Mac。您将以到目前为止所学的内容为基础,以完善构建适用于iOS...
        

    Apple发布官方SwiftUI 制作macOS App教程

    在创建了适用于watchOS的Landmarks应用程序的版本之后,是时候将您的目光投向更大的东西了:将Landmarks引入Mac。您将以到目前为止所学的内容为基础,以完善构建适用于iOS,watchOS和macOS的SwiftUI应用的经验。
    首先,将macOS目标添加到项目中,然后重新使用为iOS应用创建的共享数据。拥有所有资产后,您将创建SwiftUI视图以在macOS上显示列表视图和详细视图。
    请按照以下步骤构建该项目,或下载完成的项目以自行探索。

    教程地址

    https://developer.apple.com/tutorials/swiftui/creating-a-macos-app

    效果

    41085-e5068d0da5f1f338.jpg
    Jietu20200205-205003@2x.jpg

    更多SwiftUI教程和代码关注专栏

    QQ:3365059189
    SwiftUI技术交流QQ群:518696470

    展开全文
  • macOS 应用开发基础教程,对 macOS 平台中应用开发的 AppKit 系统控件做了详细的介绍,示例说明;从开发软件的常用的公用组件,系统框架,生产力工具开发等方面做了进一步的阐述探讨。最后介绍了几个小型项目的 demo...
  • 本博文适合有iOS APP 开发经验,并且会用swift开发人员。  环境为 xcode10.1 swift4.2    首先,Mac桌面级程序开发用的开发语言和开发工具和iOS开发都是一样,都是swift(或者Objective-C)和Xcode。  ...

          本博文适合有iOS APP 开发经验,并且会用swift的开发人员。

           环境为 xcode10.1  swift4.2

     

         首先,Mac桌面级程序开发用的开发语言开发工具和iOS开发都是一样,都是swift(或者Objective-C)和Xcode。

         同时,很多在iOS上用的第三方库(网络请求,屏幕适配等等)绝大多数都支持Mac OX开发桌面程序。所以给与了有iOS开发经验的人很大的便利,比较轻松的转到Mac桌面开发。当然,iOS和Mac OX 开发主要在系统的视图组件和一些系统API不太一样。

    本文分二部分:

    第一部分为相关的学习参考资料网址

    第二部分为工程的一个创建

     

     

    一.列举学习参考的网站

    macOSk开发入门笔记

    https://blog.csdn.net/lovechris00/article/details/72779076

     

    二.工程基本创建

    1,用Xcode 创建Mac OS app 开发,如下图: 

     

     

    2,Mac OS 开发的项目结构和iOS的非常类似,如下图

     

     

     

     

     

    3.Appdegeate 及controller

     

    展开全文
  • 使用swift通过14个迷你项目来助推macOS开发
  • 原文地址:http://footle.org/WeatherBar/ 下面开始介绍如何使用Swift开发一个Mac Menu Bar(Status Bar) App。通过做一个简单的天气app。天气数据来源于OpenWeatherMap 完成后的效果如下: 一、开始建立工程打开...

    猴子原创,欢迎转载。转载请注明: 转载自Cocos2Der-CSDN,谢谢!
    原文地址: http://blog.csdn.net/cocos2der/article/details/52054107

    这两天突然想看看OSX下的App开发,看了几篇文章。下面这一篇我觉得入门是非常好的。我仅转述为中文,并非原文翻译。原文地址:http://footle.org/WeatherBar/

    下面开始介绍如何使用Swift开发一个Mac Menu Bar(Status Bar) App。通过做一个简单的天气app。天气数据来源于OpenWeatherMap

    完成后的效果如下:
    这里写图片描述

    一、开始建立工程

    打开Xcode,Create a New Project or File ⟶ New ⟶ Project ⟶ Application ⟶ Cocoa Application ( OS X 这一栏)。点击下一步。
    这里写图片描述

    二、开始代码工作

    1. 打开MainMenu.xib,删除默认的windows和menu菜单。因为我们是状态栏app,不需要菜单栏,不需要主窗口。
      这里写图片描述

    2. 添加一个Menu菜单
      这里写图片描述
      删除其中默认的2个子菜单选项,仅保留1个。并将保留的这个改名为“Quit”。

    3. 打开双视图绑定Outlet

      • 将Menu Outlet到AppDelegate,命名为statusMenu
        这里写图片描述

      • 将子菜单Quit绑定Action到AppDelegate,命名为quitClicked
        这里写图片描述

      • 你可以删除 @IBOutlet weak var window: NSWindow! ,这个app中用不上。

    4. 代码

      • 在AppDelegate.swift中statusMenu下方添加

        let statusItem = NSStatusBar.systemStatusBar().statusItemWithLength(NSVariableStatusItemLength)
      • applicationDidFinishLaunching函数中添加:

        statusItem.title = "WeatherBar"
        statusItem.menu = statusMenu
      • 在quitClicked中添加:

        NSApplication.sharedApplication().terminate(self)
      • 此时你的代码应该如下:

    import Cocoa
    
    @NSApplicationMain
    class AppDelegate: NSObject, NSApplicationDelegate {
    
        @IBOutlet weak var statusMenu: NSMenu!
    
        let statusItem = NSStatusBar.systemStatusBar().statusItemWithLength(NSVariableStatusItemLength)
    
        @IBAction func quitClicked(sender: NSMenuItem) {
            NSApplication.sharedApplication().terminate(self)
        }
    
        func applicationDidFinishLaunching(aNotification: NSNotification) {
            statusItem.title = "WeatherBar"
            statusItem.menu = statusMenu
        }
    
        func applicationWillTerminate(aNotification: NSNotification) {
            // Insert code here to tear down your application
        }
    
    }

    运行,你可以看到一个状态栏了。

    三、进阶一步,让App变得更好

    你应该注意到了,当你运行后,底部Dock栏里出现了一个App启动的Icon。但实际上我们也不需要这个启动icon,打开Info,添加 “Application is agent (UIElement)”为YES。
    这里写图片描述

    运行一下,不会出现dock启动icon了。

    四、添加状态栏Icon

    状态栏icon尺寸请使用18x18这里写图片描述, 36x36(@2x)这里写图片描述, 54x54(@3x),添加这1x和2x两张图到Assets.xcassets中。
    这里写图片描述

    在applicationDidFinishLaunching中,修改为如下:

    let icon = NSImage(named: "statusIcon")
    icon?.template = true // best for dark mode
    statusItem.image = icon
    statusItem.menu = statusMenu

    运行一下,你应该看到状态栏icon了。

    五、重构下代码

    如果我们进一步写下去,你会发现大量代码在AppDelegate中,我们不希望这样。下面我们为Menu创建一个Controller来管理。

    • 新建一个NSObject的StatusMenuController.swift, File ⟶ New File ⟶ OS X Source ⟶ Cocoa Class ⟶ Next
      这里写图片描述

    代码如下:

    // StatusMenuController.swift
    
    import Cocoa
    
    class StatusMenuController: NSObject {
        @IBOutlet weak var statusMenu: NSMenu!
    
        let statusItem = NSStatusBar.systemStatusBar().statusItemWithLength(NSVariableStatusItemLength)
    
        override func awakeFromNib() {
            let icon = NSImage(named: "statusIcon")
            icon?.template = true // best for dark mode
            statusItem.image = icon
            statusItem.menu = statusMenu
        }
    
        @IBAction func quitClicked(sender: NSMenuItem) {
            NSApplication.sharedApplication().terminate(self)
        }
    }
    • 还原AppDelegate,修改为如下:
    // AppDelegate.swift
    
    import Cocoa
    
    @NSApplicationMain
    class AppDelegate: NSObject, NSApplicationDelegate {
        func applicationDidFinishLaunching(aNotification: NSNotification) {
            // Insert code here to initialize your application
        }
        func applicationWillTerminate(aNotification: NSNotification) {
            // Insert code here to tear down your application
        }
    }

    注意,因为删除了AppDelegate中的Outlet注册,所以你需要重新连Outlet,但在这之前我们需要先做一件事。(你可以试试连接StatusMenuController中的Outlet,看看会怎么样?)

    • 打开MainMenu.xib,添加一个Object。
      这里写图片描述
      将该Object的Class指定为StatusMenuController
      这里写图片描述
      重建Outlet到StatusMenuController,注意删除之前连接到AppDelegate的Outlet
      这里写图片描述

    当MainMenu.xib被初始化的时候,StatusMenuController下的awakeFromNib将会被执行,所以我们在里面做初始化工作。

    运行一下,保证你全部正常工作了。

    六、天气Api

    我们使用 OpenWeatherMap的天气数据,所以你得注册一个账号,获取到免费的API Key。

    • 添加WeatherAPI.swift, File ⟶ New File ⟶ OS X Source ⟶ Swift File ⟶ WeatherAPI.swift,加入如下代码,并使用你自己的API Key。
    import Foundation
    
    class WeatherAPI {
        let API_KEY = "your-api-key-here"
        let BASE_URL = "http://api.openweathermap.org/data/2.5/weather"
    
        func fetchWeather(query: String) {
            let session = NSURLSession.sharedSession()
            // url-escape the query string we're passed
            let escapedQuery = query.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())
            let url = NSURL(string: "\(BASE_URL)?APPID=\(API_KEY)&units=imperial&q=\(escapedQuery!)")
            let task = session.dataTaskWithURL(url!) { data, response, err in
                // first check for a hard error
                if let error = err {
                    NSLog("weather api error: \(error)")
                }
    
                // then check the response code
                if let httpResponse = response as? NSHTTPURLResponse {
                    switch httpResponse.statusCode {
                    case 200: // all good!
                        let dataString = NSString(data: data!, encoding: NSUTF8StringEncoding) as! String
                        NSLog(dataString)
                    case 401: // unauthorized
                        NSLog("weather api returned an 'unauthorized' response. Did you set your API key?")
                    default:
                        NSLog("weather api returned response: %d %@", httpResponse.statusCode, NSHTTPURLResponse.localizedStringForStatusCode(httpResponse.statusCode))
                    }
                }
            }
            task.resume()
        }
    }
    • 添加一个Update子菜单到Status Menu。
      这里写图片描述
      绑定Action到StatusMenuController.swift,取名为updateClicked

    • 开始使用WeatherAPI, 在StatusMenuController中let statusItem下面加入:
      let weatherAPI = WeatherAPI(),
      在updateClicked中加入:
      weatherAPI.fetchWeather("Seattle")

    注意OSX 10.11之后请添加NSAppTransportSecurity,保证http能使用。

    运行一下,然后点击Update菜单。你会收到一个json格式的天气数据。

    • 我们再调整下StatusMenuController代码, 添加一个updateWeather函数,修改后如下:
    import Cocoa
    
    class StatusMenuController: NSObject {
        @IBOutlet weak var statusMenu: NSMenu!
    
        let statusItem = NSStatusBar.systemStatusBar().statusItemWithLength(NSVariableStatusItemLength)
        let weatherAPI = WeatherAPI()
    
        override func awakeFromNib() {
            statusItem.menu = statusMenu
            let icon = NSImage(named: "statusIcon")
            icon?.template = true // best for dark mode
            statusItem.image = icon
            statusItem.menu = statusMenu
    
            updateWeather()
        }
    
        func updateWeather() {
            weatherAPI.fetchWeather("Seattle")
        }
    
        @IBAction func updateClicked(sender: NSMenuItem) {
            updateWeather()
        }
    
        @IBAction func quitClicked(sender: NSMenuItem) {
            NSApplication.sharedApplication().terminate(self)
        }
    }

    七、解析Json

    你可以使用 SwiftyJSON,但本次我们先不使用第三方库。我们得到的天气数据如下:

    {
        "coord": {
            "lon": -122.33,
            "lat": 47.61
        },
        "weather": [{
            "id": 800,
            "main": "Clear",
            "description": "sky is clear",
            "icon": "01n"
        }],
        "base": "cmc stations",
        "main": {
            "temp": 57.45,
            "pressure": 1018,
            "humidity": 59,
            "temp_min": 53.6,
            "temp_max": 62.6
        },
        "wind": {
            "speed": 2.61,
            "deg": 19.5018
        },
        "clouds": {
            "all": 1
        },
        "dt": 1444623405,
        "sys": {
            "type": 1,
            "id": 2949,
            "message": 0.0065,
            "country": "US",
            "sunrise": 1444659833,
            "sunset": 1444699609
        },
        "id": 5809844,
        "name": "Seattle",
        "cod": 200
    }
    • 在WeatherAPI.swift添加天气结构体用于解析son
    struct Weather {
        var city: String
        var currentTemp: Float
        var conditions: String
    }
    • 解析son
     func weatherFromJSONData(data: NSData) -> Weather? {
            typealias JSONDict = [String:AnyObject]
            let json : JSONDict
    
            do {
                json = try NSJSONSerialization.JSONObjectWithData(data, options: []) as! JSONDict
            } catch {
                NSLog("JSON parsing failed: \(error)")
                return nil
            }
    
            var mainDict = json["main"] as! JSONDict
            var weatherList = json["weather"] as! [JSONDict]
            var weatherDict = weatherList[0]
    
            let weather = Weather(
                city: json["name"] as! String,
                currentTemp: mainDict["temp"] as! Float,
                conditions: weatherDict["main"] as! String
            )
    
            return weather
        }
    • 修改fetchWeather函数去调用weatherFromJSONData
    let task = session.dataTaskWithURL(url!) { data, response, error in
            // first check for a hard error
        if let error = err {
            NSLog("weather api error: \(error)")
        }
    
        // then check the response code
        if let httpResponse = response as? NSHTTPURLResponse {
            switch httpResponse.statusCode {
            case 200: // all good!
                if let weather = self.weatherFromJSONData(data!) {
                    NSLog("\(weather)")
                }
            case 401: // unauthorized
                NSLog("weather api returned an 'unauthorized' response. Did you set your API key?")
            default:
                NSLog("weather api returned response: %d %@", httpResponse.statusCode, NSHTTPURLResponse.localizedStringForStatusCode(httpResponse.statusCode))
            }
        }
    }

    如果此时你运行,你会收到

    2016-07-28 11:25:08.457 WeatherBar[49688:1998824] Optional(WeatherBar.Weather(city: "Seattle", currentTemp: 51.6, conditions: "Clouds"))
    • 给Weather结构体添加一个description
    struct Weather: CustomStringConvertible {
        var city: String
        var currentTemp: Float
        var conditions: String
    
        var description: String {
            return "\(city): \(currentTemp)F and \(conditions)"
        }
    }

    再运行试试。

    八、Weather用到Controller中

    • 在 WeatherAPI.swift中增加delegate协议
    protocol WeatherAPIDelegate {
        func weatherDidUpdate(weather: Weather)
    }
    • 声明var delegate: WeatherAPIDelegate?

    • 添加初始化

    init(delegate: WeatherAPIDelegate) {
        self.delegate = delegate
    }
    • 修改fetchWeather
    let task = session.dataTaskWithURL(url!) { data, response, error in
        // first check for a hard error
        if let error = err {
            NSLog("weather api error: \(error)")
        }
    
        // then check the response code
        if let httpResponse = response as? NSHTTPURLResponse {
            switch httpResponse.statusCode {
            case 200: // all good!
                if let weather = self.weatherFromJSONData(data!) {
                    self.delegate?.weatherDidUpdate(weather)
                }
            case 401: // unauthorized
                NSLog("weather api returned an 'unauthorized' response. Did you set your API key?")
            default:
                NSLog("weather api returned response: %d %@", httpResponse.statusCode, NSHTTPURLResponse.localizedStringForStatusCode(httpResponse.statusCode))
            }
        }
    }
    • StatusMenuController添加WeatherAPIDelegate
    class StatusMenuController: NSObject, WeatherAPIDelegate {
    ...
      var weatherAPI: WeatherAPI!
    
      override func awakeFromNib() {
        ...
        weatherAPI = WeatherAPI(delegate: self)
        updateWeather()
      }
      ...
      func weatherDidUpdate(weather: Weather) {
        NSLog(weather.description)
      }
      ...
    • Callback实现,修改WeatherAPI.swift中fetchWeather:
      func fetchWeather(query: String, success: (Weather) -> Void) {
      修改fetchWeather内容
    let task = session.dataTaskWithURL(url!) { data, response, error in
        // first check for a hard error
        if let error = err {
            NSLog("weather api error: \(error)")
        }
    
        // then check the response code
        if let httpResponse = response as? NSHTTPURLResponse {
            switch httpResponse.statusCode {
            case 200: // all good!
                if let weather = self.weatherFromJSONData(data!) {
                    success(weather)
                }
            case 401: // unauthorized
                NSLog("weather api returned an 'unauthorized' response. Did you set your API key?")
            default:
                NSLog("weather api returned response: %d %@", httpResponse.statusCode, NSHTTPURLResponse.localizedStringForStatusCode(httpResponse.statusCode))
            }
        }
    }
    • 在controller中
    func updateWeather() {
        weatherAPI.fetchWeather("Seattle, WA") { weather in
            NSLog(weather.description)
        }
    }

    运行一下,确保都正常。

    九、显示天气

    在MainMenu.xib中添加子菜单 “Weather”(你可以添加2个Separator Menu Item用于子菜单分割线)
    这里写图片描述

    在updateWeather中,替换NSLog:

    if let weatherMenuItem = self.statusMenu.itemWithTitle("Weather") {
        weatherMenuItem.title = weather.description
    }

    运行一下,看看天气是不是显示出来了。

    十、创建一个天气视图

    打开MainMenu.xib,拖一个Custom View进来。

    • 拖一个Image View到Custom View中,设置ImageView宽高度为50。
      这里写图片描述

    • 拖两个Label进来,分别为City和Temperature
      这里写图片描述

    • 创建一个名为WeatherView的NSView,New File ⟶ OS X Source ⟶ Cocoa Class
      在MainMenu.xib中,将Custom View的Class指定为WeatherView
      这里写图片描述

    • 绑定WeatherView Outlet:

    import Cocoa
    
    class WeatherView: NSView {
        @IBOutlet weak var imageView: NSImageView!
        @IBOutlet weak var cityTextField: NSTextField!
        @IBOutlet weak var currentConditionsTextField: NSTextField!
    }

    并添加update:

    func update(weather: Weather) {
        // do UI updates on the main thread
        dispatch_async(dispatch_get_main_queue()) {
            self.cityTextField.stringValue = weather.city
            self.currentConditionsTextField.stringValue = "\(Int(weather.currentTemp))°F and \(weather.conditions)"
            self.imageView.image = NSImage(named: weather.icon)
        }
    }

    注意这里使用dispatch_async调用UI线程来刷新UI,因为后面调用此函数的数据来源于网络请求子线程。

    • StatusMenuController添加weatherView outlet
    class StatusMenuController: NSObject {
        @IBOutlet weak var statusMenu: NSMenu!
        @IBOutlet weak var weatherView: WeatherView!
        var weatherMenuItem: NSMenuItem!
        ...
    • 子菜单Weather绑定到视图
    weatherMenuItem = statusMenu.itemWithTitle("Weather")
    weatherMenuItem.view = weatherView
    • update中:
    func updateWeather() {
        weatherAPI.fetchWeather("Seattle, WA") { weather in
            self.weatherView.update(weather)
        }
    }

    运行一下。

    十一、添加天气图片

    先添加天气素材到Xcode,天气素材可以在http://openweathermap.org/weather-conditions 这里找到。这里我已经提供了一份icon zip, 解压后放Xcode。
    这里写图片描述

    • WeatherAPI.swift的Weather struct中,添加 var icon: String

    • 在weatherFromJSONData中:

    let weather = Weather(
        city: json["name"] as! String,
        currentTemp: mainDict["temp"] as! Float,
        conditions: weatherDict["main"] as! String,
        icon: weatherDict["icon"] as! String
    )
    • 在weatherFromJSONData:
    let weather = Weather(
        city: json["name"] as! String,
        currentTemp: mainDict["temp"] as! Float,
        conditions: weatherDict["main"] as! String,
        icon: weatherDict["icon"] as! String
    )
    • 在WeatherView的update中:
    imageView.image = NSImage(named: weather.icon)

    运行一下,Pretty!

    这里写图片描述

    十二、添加设置

    在MainMenu.xib MenuItem中,添加一个Menu Item命名为“Preferences…”
    并绑定action,命名为“preferencesClicked”

    • 添加NSWindowController命名为PreferencesWindow.swift New ⟶ File ⟶ OS X Source ⟶ Cocoa Class , 勾选同时创建XIB.在XIB中添加Label和Text Field。效果如下:
      这里写图片描述

    Outlet cityTextField到PreferencesWindow.swift

    • 在PreferencesWindow.swift中添加:
    override var windowNibName : String! {
        return "PreferencesWindow"
    }
    • windowDidLoad()中修改:
    self.window?.center()
    self.window?.makeKeyAndOrderFront(nil)
    NSApp.activateIgnoringOtherApps(true)
    • 最终PreferencesWindow.swift如下:
    import Cocoa
    
    class PreferencesWindow: NSWindowController {
        @IBOutlet weak var cityTextField: NSTextField!
    
        override var windowNibName : String! {
            return "PreferencesWindow"
        }
    
        override func windowDidLoad() {
            super.windowDidLoad()
    
            self.window?.center()
            self.window?.makeKeyAndOrderFront(nil)
            NSApp.activateIgnoringOtherApps(true)
        }
    }
    • StatusMenuController.swift中添加preferencesWindow
      var preferencesWindow: PreferencesWindow!

    • awakeFromNib中,注意在updateWeather()之前:
      preferencesWindow = PreferencesWindow()

    • preferencesClicked中:
      preferencesWindow.showWindow(nil)

    • 下面为 preferences window 添加NSWindowDelegate,刷新视图。
      class PreferencesWindow: NSWindowController, NSWindowDelegate {
      并增加

    func windowWillClose(notification: NSNotification) {
        let defaults = NSUserDefaults.standardUserDefaults()
        defaults.setValue(cityTextField.stringValue, forKey: "city")
    }

    增加协议:

    protocol PreferencesWindowDelegate {
        func preferencesDidUpdate()
    }

    增加delegate:

    var delegate: PreferencesWindowDelegate?

    在windowWillClose最下面调用

    delegate?.preferencesDidUpdate()
    • 回到StatusMenuController中,添加PreferencesWindowDelegate
    class StatusMenuController: NSObject, PreferencesWindowDelegate {

    实现代理:

    func preferencesDidUpdate() {
        updateWeather()
    }

    awakeFromNib中:

    preferencesWindow = PreferencesWindow()
    preferencesWindow.delegate = self
    • 在StatusMenuController中增加默认城市
      let DEFAULT_CITY = “Seattle, WA”

    • 修改updateWeather

    func updateWeather() {
        let defaults = NSUserDefaults.standardUserDefaults()
        let city = defaults.stringForKey("city") ?? DEFAULT_CITY
        weatherAPI.fetchWeather(city) { weather in
            self.weatherView.update(weather)
        }
    }
    • 咱们也可以在PreferencesWindow.swift windowDidLoad中设置city默认值
    let defaults = NSUserDefaults.standardUserDefaults()
    let city = defaults.stringForKey("city") ?? DEFAULT_CITY
    cityTextField.stringValue = city

    运行。一切OK。

    其他:
    - 你也可以试试使用NSRunLoop.mainRunLoop().addTimer(refreshTimer!, forMode: NSRunLoopCommonModes) 来定时updateWeather.
    - 试试点击天气后跳转到天气中心 NSWorkspace.sharedWorkspace().openURL(url: NSURL))
    - 完整工程: WeatherBar

    展开全文
  • macOS应用开发基础教程 原书 pdf 并附带Swift 4的源代码
  • macOS 开发基础教程源码!
  • DKCamera - 一个采用Swift开发的轻量级简单和轻松的相机框架
  • 基于Xcode 9, Swift 4, ORSSerialPort开发. 安装USB转串口驱动 常用的USB转串口芯片有 CH341, CP210X, PL2303, FT232等, 点击相应的名称下载macOS驱动并安装. 我这里使用的是CP2104. 安装完成后连接TFmini到macOS:...

    原作于1年前, 用USB转串口连接TFmini(北醒光子的一款Lidar)和macOS. 基于Xcode 9, Swift 4, ORSSerialPort开发.


    安装USB转串口驱动

    常用的USB转串口芯片有 CH341, CP210X, PL2303, FT232等, 点击相应的名称下载macOS驱动并安装. 我这里使用的是CP2104. 安装完成后连接TFmini到macOS:
    在这里插入图片描述

    安装USB转串口驱动后, 可以在Release中直接下载 .dmg 安装使用. 如果想知道工程怎么构建的, 可以往下读.


    新建Xcode9/Swift4工程

    新建Cocoa App工程, 命名为TFmini, 不勾选Storyboard.

    关闭工程, 打开macOS终端, 切换到TFmini Xcode工程目录, 创建Podfile, 然后用文本编辑打开:

    touch Podfile
    open -a TextEdit Podfile
    

    填入以下代码:

    target "TFmini"
    pod "ORSSerialPort"
    

    其中target后面是工程名, 保存关闭.

    使用Cocoapods导入:

    pod install
    

    完成后关闭终端, 打开工程目录下的 .xcworkspace 文件. 点击顶部的黄色小叹号, 更新到推荐的设置.

    手动添加OC到Swift的桥接文件: TFmini-Bridging-Header.h, 其中TFmini是工程名, 内容为:

    #import "ORSSerialPort.h"
    #import "ORSSerialPortManager.h"
    

    依次点击 工程名 -> Targets下的工程名 -> Build Setting -> Swift Compiler -General -> Objective-C Bridging Header, 双击右侧空白处, 填入以下代码后回车:

    $(PROJECT_DIR)/$(PROJECT_NAME)/$(PROJECT_NAME)-Bridging-Header.h
    

    如图所示:

    在这里插入图片描述

    新建一个TFmini类, 继承于NSObject.

    然后在XIB文件中加入一个 Object, 在Identity Inspector中的Class, 选择刚刚创建的TFmini类, 这样, 就可以关联对象到TFmini类中了:

    在这里插入图片描述

    拖Label, Pop Up Button, Push Button, TextView各种控件到Window的View中, 给控件添加一些约束, 并设置Window的最小尺寸为480*360:

    在这里插入图片描述

    关联Open按钮和TextView接收框到TFmini中, 关联Open按钮的点击事件, 注意 NSTextView, 需要连点3下才能选中, 别拖成ScrollView或者ClipView了:

        @IBOutlet weak var openCloseButton: NSButton!
        @IBOutlet var receivedDataTextView: NSTextView!
        
        @IBAction func openOrClosePort(_ sender: Any) {
        }
    

    把其余的代码添加进来, 主要是 ORSSerialPortDelegate, NSUserNotificationCenterDelegate的一些实现.
    变量前的 @objc dynamic 是后面的Binding必须的.
    TFmini的数据解析在 func serialPort(_ serialPort: ORSSerialPort, didReceive data: Data) 中, 数据接收超过10000帧, 会清空缓存.
    参考工程源文件.

    接下来就是Binding了.

    串口弹出按钮的Binding:

    在这里插入图片描述

    波特率弹出按钮的Binding:

    在这里插入图片描述

    打开关闭按钮的Binding, 保证没有串口时不可被选中:

    在这里插入图片描述

    设置完毕, 运行, 串口号选择SLAB_USBtoUART, 波特率选择115200, open报错:

    SerialPort SLAB_USBtoUART encountered an error: Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted" UserInfo={NSFilePath=/dev/cu.SLAB_USBtoUART, NSLocalizedDescription=Operation not permitted}
    

    修改 工程名.entitlements, 增加 com.apple.security.device.serial, 设为YES, 如果想要应用上传到AppStore, 这个是必须的:

    在这里插入图片描述

    确保Team有效:

    在这里插入图片描述

    依次点击工程名 -> Targets下的工程名 -> Capbilities -> Keychain Sharing -> 打开开关.

    再次运行, 就可以了:

    在这里插入图片描述
    左边是原始的9字节十六进制数, 右边是计算出来的实际距离值, 单位cm.

    Github链接

    https://github.com/TFmini/TFmini-macOS

    展开全文
  • MacOS上键盘/鼠标控制应用的Swift语言开发笔记 背景 继续在做小gadget,先学习基本操作。 这次需要实现的功能是程序控制键盘和鼠标,也就是人不需要碰键盘鼠标而键盘自动输入,鼠标自动移动点击的功能。 网上...

    MacOS上键盘/鼠标控制应用的Swift语言开发笔记

    背景

    继续在做小gadget,先学习基本操作。
    这次需要实现的功能是程序控制键盘和鼠标,也就是人不需要碰键盘鼠标而键盘自动输入,鼠标自动移动点击的功能。

    网上搜索了一下,Objective-C的实现例子倒是不少,可是基本找不到太多讲swift上面实现的例子,无奈自行摸索,在此总结一下。

    准备

    本功能应该属于Accessibility(辅助功能)的范畴,测试需要给予Xcode相应的操作权限。
    打开【系统编好设置】,【安全性与隐私】,【隐私】里面勾选Xcode前面的方框。如果看不到Xcode的话手动添加。

    库和官方文件

    需要用到Core Graphics里面的
    * Quartz Event Services | Apple Developer Documentation
    * Quartz Display Services | Apple Developer Documentation

    按键事件控制键盘

    首先定义所需要的按键事件,然后通过post方法让系统执行事件。
    每一次按键需要先按下再离开,通过函数分别定义这两个操作。

    属性方面:

    • 这里的CGEventSourceStateID看了一下官方文件,分privateState,combinedSessionState和hidSystemState三种,这里选择最后一个,一般source定义为nil也可以。
    • virtualKey后面是需要的按键的代码,mac的英语键盘的话参考文末的一览表。
    • keyDown当然true是按下,false是松开了
    • tap后面是按键时候的鼠标位置
    //按下按键
    func keyboardKeyDown(key: CGKeyCode) {
    
            let source = CGEventSource(stateID: CGEventSourceStateID.hidSystemState)
            let event = CGEvent(keyboardEventSource: source, virtualKey: key, keyDown: true) 
            event?.post(tap: CGEventTapLocation.cghidEventTap)
            print("key \(key) is down")
        }
    
    //松开按键    
    func keyboardKeyUp(key: CGKeyCode) {
            let source = CGEventSource(stateID: CGEventSourceStateID.hidSystemState)
            let event = CGEvent(keyboardEventSource: source, virtualKey: key, keyDown: false)
            event?.post(tap: CGEventTapLocation.cghidEventTap)
            print("key \(key) is released")
        }

    按键例子

    比如说需要按下F5的话,调用上面两个函数就可以

    keyboardKeyDown(key: 0x60) //0x60是F5功能键代码
    keyboardKeyUp(key: 0x60)

    Command+C怎么办?

    这时候加入CGEventFlags - Core Graphics | Apple Developer Documentation

    let cmd_c_D = CGEventCreateKeyboardEvent(nil, 0x08, true); //0x08是C键代码 cmd-c down
    CGEventSetFlags(cmd_c_D, CGEventFlags.MaskCommand);
    CGEventPost(CGEventTapLocation.CGHIDEventTap, cmd-c-D);
    
    let cmd_c_U = CGEventCreateKeyboardEvent(nil, 0x08, false); // cmd-c up
    CGEventSetFlags(cmd_c_U, CGEventFlags.MaskCommand);
    CGEventPost(CGEventTapLocation.CGHIDEventTap, cmd_c_U);

    shift,control等都有相应的CGEventSetFlags

    鼠标控制

    鼠标控制注意

    • 移动鼠标和显示移动后的鼠标是不同的事件操作。
    • 还有注意显示器的坐标轴原点不是左上角,而是左下角。留意别搞错y轴的方向了。

    移动点击鼠标事件

    // 鼠标左键按下
    guard let mouseDown = CGEvent(mouseEventSource: nil,
                            mouseType: .leftMouseDown,
                            mouseCursorPosition: CGPoint(x: 200, y: 300),
                            mouseButton: .left
                            ) else {return}
    mouseDown?.post(tap: .cghidEventTap)
    
    // 鼠标左键抬起
    guard let mouseUp = CGEvent(mouseEventSource: nil,
                          mouseType: .leftMouseUp,
                          mouseCursorPosition: CGPoint(x: 200, y: 300),
                          mouseButton: .left
                          ) else {return}
    mouseUp?.post(tap: .cghidEventTap)

    属性一览表:CGEventType - Core Graphics | Apple Developer Documentation

    只移动鼠标

    guard let moveEvent = CGEvent(mouseEventSource: nil, mouseType: .mouseMoved, 
                          mouseCursorPosition: point, mouseButton: .left 
                          ) else {return}
    moveEvent?.post(tap: .cghidEventTap)

    移动屏幕上面的鼠标图标

    利用上面的事件移动鼠标之后,屏幕上的鼠标图标是不动的。需要再用下面操作才能看到鼠标的图标在屏幕上面移动了。

    func CGDisplayMoveCursorToPoint(_ display: CGDirectDisplayID, 
                                  _ point: CGPoint) -> CGError

    完整的鼠标移动函数

    func moveMouse(_ dx:CGFloat , _ dy:CGFloat){
      //先监控移动前鼠标位置
      var mouseLoc = NSEvent.mouseLocation  
      mouseLoc.y = NSHeight(NSScreen.screens[0].frame) - mouseLoc.y;
      //计算鼠标新位置
      let newLoc = CGPoint(x: mouseLoc.x-CGFloat(dx), y: mouseLoc.y+CGFloat(dy)) 
      print("moving \(dx) \(dy)")
      CGDisplayMoveCursorToPoint(0, newLoc)
        }

    其他先留坑

    附录

    MouseType类型

    /* Constants that specify the different types of input events. */
    public enum CGEventType : UInt32 {
    
    
        /* The null event. */
        case null
    
    
        /* Mouse events. */
        case leftMouseDown
    
        case leftMouseUp
    
        case rightMouseDown
    
        case rightMouseUp
    
        case mouseMoved
    
        case leftMouseDragged
    
        case rightMouseDragged
    
    
        /* Keyboard events. */
        case keyDown
    
        case keyUp
    
        case flagsChanged
    
    
        /* Specialized control devices. */
        case scrollWheel
    
        case tabletPointer
    
        case tabletProximity
    
        case otherMouseDown
    
        case otherMouseUp
    
        case otherMouseDragged
    
    
        /* Out of band event types. These are delivered to the event tap callback
           to notify it of unusual conditions that disable the event tap. */
        case tapDisabledByTimeout
    
        case tapDisabledByUserInput
    }

    Mac的键盘代码一览

    /*
     *  Summary:
     *    Virtual keycodes
     *  
     *  Discussion:
     *    These constants are the virtual keycodes defined originally in
     *    Inside Mac Volume V, pg. V-191. They identify physical keys on a
     *    keyboard. Those constants with "ANSI" in the name are labeled
     *    according to the key position on an ANSI-standard US keyboard.
     *    For example, kVK_ANSI_A indicates the virtual keycode for the key
     *    with the letter 'A' in the US keyboard layout. Other keyboard
     *    layouts may have the 'A' key label on a different physical key;
     *    in this case, pressing 'A' will generate a different virtual
     *    keycode.
     */
    enum {
      kVK_ANSI_A                    = 0x00,
      kVK_ANSI_S                    = 0x01,
      kVK_ANSI_D                    = 0x02,
      kVK_ANSI_F                    = 0x03,
      kVK_ANSI_H                    = 0x04,
      kVK_ANSI_G                    = 0x05,
      kVK_ANSI_Z                    = 0x06,
      kVK_ANSI_X                    = 0x07,
      kVK_ANSI_C                    = 0x08,
      kVK_ANSI_V                    = 0x09,
      kVK_ANSI_B                    = 0x0B,
      kVK_ANSI_Q                    = 0x0C,
      kVK_ANSI_W                    = 0x0D,
      kVK_ANSI_E                    = 0x0E,
      kVK_ANSI_R                    = 0x0F,
      kVK_ANSI_Y                    = 0x10,
      kVK_ANSI_T                    = 0x11,
      kVK_ANSI_1                    = 0x12,
      kVK_ANSI_2                    = 0x13,
      kVK_ANSI_3                    = 0x14,
      kVK_ANSI_4                    = 0x15,
      kVK_ANSI_6                    = 0x16,
      kVK_ANSI_5                    = 0x17,
      kVK_ANSI_Equal                = 0x18,
      kVK_ANSI_9                    = 0x19,
      kVK_ANSI_7                    = 0x1A,
      kVK_ANSI_Minus                = 0x1B,
      kVK_ANSI_8                    = 0x1C,
      kVK_ANSI_0                    = 0x1D,
      kVK_ANSI_RightBracket         = 0x1E,
      kVK_ANSI_O                    = 0x1F,
      kVK_ANSI_U                    = 0x20,
      kVK_ANSI_LeftBracket          = 0x21,
      kVK_ANSI_I                    = 0x22,
      kVK_ANSI_P                    = 0x23,
      kVK_ANSI_L                    = 0x25,
      kVK_ANSI_J                    = 0x26,
      kVK_ANSI_Quote                = 0x27,
      kVK_ANSI_K                    = 0x28,
      kVK_ANSI_Semicolon            = 0x29,
      kVK_ANSI_Backslash            = 0x2A,
      kVK_ANSI_Comma                = 0x2B,
      kVK_ANSI_Slash                = 0x2C,
      kVK_ANSI_N                    = 0x2D,
      kVK_ANSI_M                    = 0x2E,
      kVK_ANSI_Period               = 0x2F,
      kVK_ANSI_Grave                = 0x32,
      kVK_ANSI_KeypadDecimal        = 0x41,
      kVK_ANSI_KeypadMultiply       = 0x43,
      kVK_ANSI_KeypadPlus           = 0x45,
      kVK_ANSI_KeypadClear          = 0x47,
      kVK_ANSI_KeypadDivide         = 0x4B,
      kVK_ANSI_KeypadEnter          = 0x4C,
      kVK_ANSI_KeypadMinus          = 0x4E,
      kVK_ANSI_KeypadEquals         = 0x51,
      kVK_ANSI_Keypad0              = 0x52,
      kVK_ANSI_Keypad1              = 0x53,
      kVK_ANSI_Keypad2              = 0x54,
      kVK_ANSI_Keypad3              = 0x55,
      kVK_ANSI_Keypad4              = 0x56,
      kVK_ANSI_Keypad5              = 0x57,
      kVK_ANSI_Keypad6              = 0x58,
      kVK_ANSI_Keypad7              = 0x59,
      kVK_ANSI_Keypad8              = 0x5B,
      kVK_ANSI_Keypad9              = 0x5C
    };
    
    /* keycodes for keys that are independent of keyboard layout*/
    enum {
      kVK_Return                    = 0x24,
      kVK_Tab                       = 0x30,
      kVK_Space                     = 0x31,
      kVK_Delete                    = 0x33,
      kVK_Escape                    = 0x35,
      kVK_Command                   = 0x37,
      kVK_Shift                     = 0x38,
      kVK_CapsLock                  = 0x39,
      kVK_Option                    = 0x3A,
      kVK_Control                   = 0x3B,
      kVK_RightShift                = 0x3C,
      kVK_RightOption               = 0x3D,
      kVK_RightControl              = 0x3E,
      kVK_Function                  = 0x3F,
      kVK_F17                       = 0x40,
      kVK_VolumeUp                  = 0x48,
      kVK_VolumeDown                = 0x49,
      kVK_Mute                      = 0x4A,
      kVK_F18                       = 0x4F,
      kVK_F19                       = 0x50,
      kVK_F20                       = 0x5A,
      kVK_F5                        = 0x60,
      kVK_F6                        = 0x61,
      kVK_F7                        = 0x62,
      kVK_F3                        = 0x63,
      kVK_F8                        = 0x64,
      kVK_F9                        = 0x65,
      kVK_F11                       = 0x67,
      kVK_F13                       = 0x69,
      kVK_F16                       = 0x6A,
      kVK_F14                       = 0x6B,
      kVK_F10                       = 0x6D,
      kVK_F12                       = 0x6F,
      kVK_F15                       = 0x71,
      kVK_Help                      = 0x72,
      kVK_Home                      = 0x73,
      kVK_PageUp                    = 0x74,
      kVK_ForwardDelete             = 0x75,
      kVK_F4                        = 0x76,
      kVK_End                       = 0x77,
      kVK_F2                        = 0x78,
      kVK_PageDown                  = 0x79,
      kVK_F1                        = 0x7A,
      kVK_LeftArrow                 = 0x7B,
      kVK_RightArrow                = 0x7C,
      kVK_DownArrow                 = 0x7D,
      kVK_UpArrow                   = 0x7E
    };

    参考和其他注意事项:

    let opts = NSDictionary(object: kCFBooleanTrue,
                            forKey: kAXTrustedCheckOptionPrompt.takeUnretainedValue() as NSString
                            ) as CFDictionary
    
    guard AXIsProcessTrustedWithOptions(opts) == true else { return }

    վ HᴗP ի

    展开全文
  • Swift iOS macOS 多语言支持,国际化支持, Localization Localizable 完成一个项目之后,如果你想让全世界的人都能使用这个 app,字符串国际化、本地化是一步必不可少的步骤。 具体看这里,懒得再翻译一遍了,一个...
  • macOS应用开发基础教程 张帆+随书源码,书写的很好,如果条件允许,请支持下作者
  • 原文:How To Make A UIViewController Transition Animation Like in the Ping App ... 更新说明:本教程由 Luke Parhm 更新至 Xcode 9/Swift 4。原文作者是 Rounak Jain。 不久前,匿名社交网络 app Secret 的...
  • macOS的menuBar功能非常强大,我们在开发macOS应用的时候,经常需要利用menuBar实现功能。这篇文章主要是关于如何用swiftUI优雅地现在menuBar的popover。 最终效果图: 创建Popover 在AppDelegate文件中,...
  • macos开发 支持http网页

    2019-12-05 11:41:58
    macos开发默认不支持http网页的链接。需要如下设置 解决 打开 info.plist 文件,右键open as->source code ,输入 <key>NSAppTransportSecurity</key> <dict> <key>...
  • Swift开发Mac App(1)

    2017-06-28 10:41:24
    原文超级详细(图文),小白表示毫无压力:http://www.raywenderlich.com/87002/getting-started-with-os-x-and-swift-tutorial-part-1由于原文浅显到略显啰嗦,因此翻译时我有选择地删除了部分段落。打开Xcode,...
  • macOS与iOS开发的最大区别就在于界面size变的巨大了,之前我们在iOS小屏的经验很难迁移到macOS上。本文将带领大家掌握macOS最基础的界面框架,三栏架构。 解决方案和代码 解决方案 NavigationView实现分栏目 frame ...
  • Swift开发Mac App(6)

    2015-05-07 09:28:47
    接下来实现EDStarRating 的编辑。 在loadView 方法中,我们已经配置了...打开MasterViewController.swift 在 EDStarRatingProtocol 扩展中添加如下方法: func starsSelectionChanged(control: EDStarRating!,
  • 1、在文本编辑器的菜单的“格式”选项中我们可以找到“制作纯文本”这个选项,对应的快捷键是shift⇧+command⌘+T 2、在xcode中,新建一个文本文件就ok了。
  • 苹果开发,实现文件读写,按读可以读取文件内容显示出来。按另外一个button是将输入框中的字符写入文件中。文件路径是写死的,且需要提前创建好。
1 2 3 4 5 ... 20
收藏数 3,028
精华内容 1,211
热门标签
关键字:

macos swift 开发