了解如何从头开始开发自己的macOS应用程序!
您是否想学习如何开发自己的macOS应用程序,并以此为荣并在您的个人MacBook上使用它? 或者,也许您有激动的热情开始在Mac上进行开发? 然后,您来对地方了! 在这里,我将引导您完成使用最现代的语言之一Swift开发第一个非常macOS应用程序的步骤。
- 对编程有兴趣
- 对Swift编程有一些基本的了解(优势)
- 已安装Xcode 8.3.3
- 热情打造macOS应用
- macOS开发的基本概念
- 如何将Alamofire与macOS应用程序集成以执行网络呼叫
- 如何创建拖放机制
- 一些Swift 3语法
我敢肯定,您很高兴知道我们将制造什么好东西! 在本教程中,我们将研究Mac的主要应用程序层Cocoa。 该层负责应用程序的外观和用户动作的响应能力,这也是我们将引入所有可视元素,网络和应用程序逻辑的地方。 我们会将图像上传到uploads.im,因为它提供了供我们使用的开放API。 完成整个教程后,这将是最终产品,
首先将向用户显示一个主屏幕,并显示“在此处拖放图像”的说明。 然后,用户可以将任何 jpg 格式的图像 拖动 到应用程序中,然后该应用程序将显示加载微调框,以通知用户它正在将图像上传到服务器。 服务器 成功 响应后 ,用户将获得一个弹出警报窗口,可以在其中 将URL复制到剪贴板 ,然后用户可以将URL粘贴到任何地方,例如在Internet浏览器上从服务器查看其图像。
聊够了,让我们开始吧!
首先,让我们启动Xcode 9并创建名为PushImage
macOS应用项目。 在Application
下选择macOS
和Cocoa App
。
您可以按照我在下一页中输入的设置进行操作,然后为您的项目文件选择一个目录。
现在您已经完成了所有项目的设置,我想借此机会在Xcode 9中重构我最喜欢的一项新功能, 重构! 如果您使用过以前版本的Xcode,则无法重构Swift代码。 让我们试一试。 突出显示ViewController
并转到Editor->Refactor
。 让我们将其更改为HomeViewController
,然后单击Rename
。 这将对您的filename
, class name
和Storyboard viewcontroller class name
进行全局更改, Storyboard viewcontroller class name
酷了!
回到我们的HomeViewController
类,您将注意到该控制器是NSViewController
的子类。 如果您来自开发iOS应用程序,那么您会立刻注意到我们使用NS
而不是UI
的区别。 那是因为我们使用的是Cocoa (AppKit)
而不是Cocoa Touch (UIKit)
。 我认为Apple制定了这两个截然不同的框架,主要是为了将mobile
和OS
背后的技术分开。 drag and drop
技术在移动设备上没有用,但在OS
上非常有用。 简而言之, UIKit
是AppKit
的精简版。
您可以从此链接下载云图像图标。 拥有资产后,将其拖动到Assets.xcassets
, Assets.xcassets
重命名为uploadCloud
并将资产移动到2x
。
接下来,让我们移至Main.Storyboard,以创建与用户进行交互的视图。 我们将使用Xcode的漂亮界面生成器来设计主页的外观。 在右下角的容器中,转到第三个项目(即object library
并搜索NSImageView
,然后将其拖入HomeViewController
视图的中心。
现在,单击您刚刚拖动一次的NSImageView
,然后转到第四项,这是“ Attributes Inspector
,将“ Image
设置为uploadCloud
。 因为在最后一步中,我们将其重命名为uploadCloud
,Xcode会自动注册该名称,我们可以通过在此处插入名称直接使用它,而无需指定其文件扩展名。 Xcode的奇迹之一!
让我们也稍微增加一下宽度和高度,以便整体上看起来更好。 单击右侧(实用程序)面板中的第五项。 这是Size Inspector
并将其宽度和高度增加到150x150
。
我们还将需要一个文本来告诉用户他们需要做什么,所以现在继续搜索label
,并将其放在imageView
下方。 再次,转到“ Utilities
面板,在“ Attributes Inspector
,将“ Title
设置为Drag and Drop a .jpg image file here
。 您会看到文本被完全截断,让我们在Size Inspector
中将宽度增加到300
,并将Text Alignment
设置为Center-aligned
。
现在,通过先单击imageView
,然后按住CMD
并单击label
,将imageView
和label
都对准窗口的中心。 这应该让您突出显示这两个元素,然后将它们拖动到看到一个十字,如果组件居中,该十字准线将为您准确指示。
我们要添加的倒数第二个UI组件是Indicator
。 继续搜索并将其拖到窗口中,放置在标签中心。
我们需要的最后一个组件是最重要的部件, DragView
! 我们需要一个在我们的应用中充当可放置区域的View
。 因此,遵循与搜索组件相同的习惯,搜索NSView
并将其拖入,然后对其进行扩展,以使其填充整个窗口。 Storyboard
从下至上或LastInFirstOut
概念对图层进行LastInFirstOut
。 因此,现在,我们的视图涵盖了所有其他视图组件。 稍后,我们将视图设置为透明背景,并且不会遮挡其下方的任何视图。
要走的路! 这就是我们需要添加到Storyboard
所有组件,现在让我们连接Outlets,这是接口构建器用来连接到其ViewController
声明的属性的引用。
使用Storyboard
作为活动窗口,我们可以通过激活位于右上方的Assistant Editor
来打开一个单独的窗口,其中显示HomeViewController
类。
按住ctrl
,单击ImageView
并将其拖动到类声明的下面,我们将其称为imageView
。 大! 现在,您的imageView
插座已连接到我们类中的属性,我们现在可以通过编程方式调整其属性并使用它。
挑战时间
现在,尝试对view
, label
和indicator
进行相同的dragView
,分别loadingSpinner
为dragView
, staticLabel
和loadingSpinner
。 您的代码最后应该看起来像这样。
导入Cocoaclass HomeViewController:NSViewController {
@IBOutlet弱var imageView:NSImageView!
@IBOutlet弱var staticLabel:NSTextField!
@IBOutlet弱var loadingSpinner:NSProgressIndicator!
@IBOutlet弱变量dragView:NSView!覆盖func viewDidLoad(){
super.viewDidLoad()//加载视图后执行任何其他设置。
}覆盖var namedObject:可以吗? {
didSet {
//更新视图(如果已加载)。
}
}
}
要在窗口中腾出一些空间,让我们回到“标准编辑器”,方法是单击右上角的第一项,我们将移至下一个任务。
创建我们的拖动视图
我们的下一个任务确实是使我们的DragView
成为.jpg
图像文件的适当“放下”点。 继续, right-click
您的根文件夹,然后选择New File
。 选择Cocoa Class
并创建一个新的NSView
DragView
子NSView
。 我们还需要返回到Storyboard
,并将DragView
的类设置为DragView
。 然后回到我们的HomeViewController
并更改NSView!
到DragView
!
@IBOutlet弱var dragView:DragView!
如果我们仔细观察一下NSView
,我们发现它实际上自动符合NSDraggingDestination
协议,这正是我们需要“下车”空间的条件。
在使用所有可拖动方法之前,我们首先需要register
我们的视图。 用以下代码替换视图类中的代码:
是否需要初始化?(编码器:NSCoder){
super.init(编码器:编码器)
registerForDraggedTypes([NSPasteboard.PasteboardType
.fileNameType(forPathExtension:“ .jpg”)])
}
这行代码为文件中注册的任何带有文件扩展名“ .jpg”的拖动项目注册视图。 我们所做的只是注册,我们的视图尚未准备好接受文件。
遵循NSDraggingDestination文档,我们可以得出结论,我们需要这些功能来实现所需的功能:
- draggingEntered
- draggingUpdated
- performDragOperation
导入Cocoaclass DragView:NSView {
// 1
私人var fileTypeIsOk =否
私人var acceptedFileExtensions = [“ jpg”]
是否需要初始化?(编码器:NSCoder){
super.init(编码器:编码器)
register(forDraggedTypes:[NSFilenamesPboardType])
}
// 2
覆盖func draggingEntered(_发送者:NSDraggingInfo)-> NSDragOperation {
fileTypeIsOk = checkExtension(拖动:发件人)
返回[]
}
// 3
覆盖func draggingUpdated(_ sender:NSDraggingInfo)-> NSDragOperation {
返回fileTypeIsOk吗? .copy:[]
}
// 4
覆盖func performDragOperation(_ sender:NSDraggingInfo)-> Bool {
警卫队让draggedFileURL = sender.draggedFileURL else {
返回假
}
返回真
}
// 5
fileprivate func checkExtension(拖动:NSDraggingInfo)->布尔{
保护卫队fileExtension = drag.draggedFileURL?.pathExtension?.lowercased()else {
返回假
}
返回acceptedFileExtensions.contains(fileExtension)
}} // 6
扩展名NSDraggingInfo {
var draggedFileURL:NSURL? {
让文件名= draggingPasteboard()。propertyList(forType:NSFilenamesPboardType)为? [串]
让路径=文件名?
返回path.map(NSURL.init)
}
}
1-首先,我们需要创建一个boolean flag
调用fileTypeIsOk
,默认为false
以帮助我们仅推进图像的正确文件格式。 我们还创建了一个acceptedFileExtensions
,它是string
格式的可接受文件格式的数组。
2-当文件首次进入“放置区域”时,将调用draggingEntered
函数。 在这里,我们将调用函数checkExtension
,稍后将进行讨论,如果文件类型为.jpg
,则将我们的fileTypeIsOk
布尔值设置为true
否则将其设置为false
。
3-在这里实现draggingUpdated
函数以获取图像的详细信息。 在这种情况下,如果为fileTypeIsOk
,我们将返回图像的copy
,否则它将返回由[]
表示的empty
数据。
4 —用户释放鼠标后,将调用performDragOperation
函数,稍后我们将利用此函数将URL传递给HomeViewController
。
5 – checkExtension
是我们的“自制”函数,我们在其中检查drag
对象,获取进入的文件的url
,并检查其是否符合我们的acceptedFileExtensions
。
6 —在这里,我们扩展了NSDraggingInfo
,它实际上是我们在draggingEntered
和draggingUpdated
看到的所有发件人。 我们在此处添加了一个名为draggedFileURL
的变量,以引用图像文件的url。
如果现在运行该应用程序,则应该可以将带有.jpg
的图像文件拖入并在光标处看到绿色的+号,但对于其他文件类型则不能。 大! 现在,我们知道我们的应用仅正确接受特定的文件类型,让我们继续在view
和controller
之间建立通信。
创建我们的委托委派模式是Cocoa编程中最常见的模式之一。 这就像创建一个命令中心来广播A
所做的事情,因此现在B
应该这样做。 简而言之,我们将创建以下这些:
- 我们的指挥中心(DragViewDelegate)
- 函数调用
didDragFileWith
- 查看以保存对
DragViewDelegate
的订户的引用,并调用didDragFileWith
- 当在
View
调用DragViewDelegate
,ViewController订阅DragViewDelegate
可以执行某些操作。
因此,让我们回到DragView
并在import Cocoa
下方的类声明顶部输入以下代码:
通讯协定DragViewDelegate {
func dragView(didDragFileWith URL:NSURL)
}
我们在此处创建一个委托协议,该协议定义了委托要命令的职责。 在此,唯一的责任是当dragView收到事件调用dragViewDidDragFileWith
,将调用委托,并且订阅者将做出反应。 因此,我们还为订户创建一个变量引用,并将其放在类声明的开头括号之后:
类DragView:NSView {
var委托:DragViewDelegate吗?
我们要立即通知HomeViewController
放置了正确的文件,因此,在用户释放其拖动事件以及所有检查完成后,委托广播的最佳位置是在performDragOperation
中。 因此,继续添加以下代码行:
覆盖func performDragOperation(_ sender:NSDraggingInfo)-> Bool {
警卫队让draggedFileURL = sender.draggedFileURL else {
返回假
}
//呼叫代表
如果fileTypeIsOk {
委托?.dragView(didDragFileWith:draggedFileURL)
}
返回真
}
现在让我们回到HomeViewController
并扩展我们的类以实现我们的delegate
方法。 继续并添加以下代码行:
扩展HomeViewController:DragViewDelegate {
func dragView(didDragFileWith URL:NSURL){
打印(URL.absoluteString)
}
}
如果我们现在运行该应用程序,我们将期望看到在控制台中打印出我们的文件URL,请尝试一下!
嗯……当我们将.jpg
文件拖入时,什么也没发生,我们错过了什么吗? 是! 我们需要HomeViewController
来订阅View
的委托。 这是开发人员忘记添加订户的常见遗漏,因此让我们继续进行修复。 在viewDidLoad()
添加它:
覆盖func viewDidLoad(){
super.viewDidLoad()
dragView.delegate =自我
}
完成之后,再次运行该应用程序,您将看到打印的URL。 做得好! 您应该走这么远,这在您的背上值得一拍。 我们快到了,我们现在可以将文件推送到服务器了!
Alamofire是一个用Swift编写的功能强大的网络库。 以前称为AFNetworking ,它是迄今为止维护最完善的网络库。
使用迦太基安装Alamofire
随着迦太基图书馆的发展,我对迦太基的热爱已经超过了Cocoapods,因为它更加分散,当一个框架无法构建时,整个项目仍然可以编译。 如果您没有使用迦太基的经验,请查看此链接。
我们首先需要创建一个CartFile.private
。 您可以打开Terminal
并导航到项目目录。 {ROOT}/PushImage
。 然后运行vim CartFile.private
。 点击i
以Insert
,然后复制下面的行。然后点击esc
并执行:wq
。 vim
是轻量级的编辑器,开发人员通常在诸如Terminal
, Linux
命令提示符环境中使用。
#CartFile.private
github的“ Alamofire / Alamofire”〜> 4.5
现在运行carthage update
。 这将克隆依赖项,您应该在Carthage -> Build -> Mac
文件夹中获得Alamofire框架。 您应该在最后看到此屏幕:
接下来,导航至Carthage -> Build -> Mac` and drag in Alamofire Framework
拖入我们项目的Linked Frameworks and Libraries
。
您还需要转到“构建阶段”,单击左上角的加号,然后选择“新建复制文件阶段”。 将目的地设置为“ Frameworks”,然后添加Alamofire。
要走的路! 现在,您已经设置了所有网络库!
遵循此处编写的api document
。 我们将利用:
POST方法发送的uploadURL或图像。 系统自动检测到您已上传。
就像文档中给出的示例一样执行POST
。
因此,让我们继续并在dragView
函数中添加以下代码行:
进口可可
进口Alamofire
扩展HomeViewController:DragViewDelegate {
func dragView(didDragFileWith URL:NSURL){
Alamofire.upload(multipartFormData:{(data:MultipartFormData)在
data.append(URL为URL,withName:“ upload”)
},以:“ http://uploads.im/api?format=json”){[弱自我](encodingResult)在
切换encodingResult {
大小写.success(让上载_,_):
upload.responseJSON {在
守护
让dataDict = response.result.value为? NSDictionary,
让data = dataDict [“ data”]为? NSDictionary,
让imgUrl = data [“ img_url”]为? 其他字符串{return}
打印(imgUrl)
}
情况.failure(let encodingError):
打印(encodingError)
}
}
}
}
那是很多代码!!! 别害怕,特别感谢Alamofire,该库为我们提供了一个单一的“上载”函数调用,该函数允许我们使用[multipartFormData,这是一种通常用于上传文件的联网方法)来发布文件。 在这里,我们附加了文件的url,这是文件在字符串中的给定位置,作为带有参数名称upload
的URL
对象传入。
端点http://uploads.im/api?format=json
附加了query parameter
format=json
,还可以指定我们想要的格式响应。 当服务器响应upload success
完成upload success
,我们将通过解析JSON响应来获取img_url
,并将其打印出来。 试试吧!
糟糕! 当我们在应用程序中进行第一个网络调用时,我们可能还会看到另一个常见的错误消息。 就像这样:
由于不安全,App Transport Security阻止了明文HTTP(http://)资源加载。 可以通过应用程序的Info.plist文件配置临时异常。
苹果最近推出了这项新规定,所有应用必须通过https
协议进行通信。 由于我们只是在进行实验并将其供个人使用,因此我们可以执行解决方法。
转到项目目录面板上的Info.plist
,右键单击它并选择open作为source code
并添加以下代码:
NSPrincipalClass
NSApplication
//
NSAppTransportSecurity
NSAllowsArbitraryLoads
//
这将绕过ATS
并允许我们与非https协议进行通信。 运行该应用程序,然后尝试上传另一个图像文件! 几秒钟后,您应该会在控制台上看到打印的文件URL! 继续并将其粘贴到浏览器中,以查看现在已从uploads.im服务器加载的本地计算机上的图像!
我们知道我们的Image Loader即将完成,但是我们需要添加一些其他的UX(用户体验)以使其更加完整。
之前我们讨论了加载微调器,所以让我们继续执行加载微调器的逻辑以:
1.在应用启动时隐藏
2.上传时显示动画
3.上传结束或失败时隐藏和停止动画
在我们的viewDidLoad()
添加它:
覆盖func viewDidLoad(){
super.viewDidLoad()
dragView.delegate =自我
loadingSpinner.isHidden = true
}
上传时显示和动画
上传完成或失败时隐藏和停止动画
func dragView(didDragFileWith URL:NSURL){
loadingSpinner.isHidden = false
loadingSpinner.startAnimation(self.view)
Alamofire.upload(multipartFormData:{(data:MultipartFormData)在
data.append(URL为URL,withName:“ upload”)
},以:“ http://uploads.im/api?format=json”){[弱自我](encodingResult)在
切换encodingResult {
大小写.success(让上载_,_):
upload.responseJSON {在
守护
让dataDict = response.result.value为? NSDictionary,
让data = dataDict [“ data”]为? NSDictionary,
让imgUrl = data [“ img_url”]为? 其他字符串{return}
self?.loadingSpinner.isHidden = true
self?.loadingSpinner.stopAnimation(self?.view)
}
情况.failure(let encodingError):
打印(encodingError)
}
}
}
将它们添加到我们的dragView
函数中。 运行该应用程序并拖入一个图像文件,开始上传后,您应该会看到正在运行的加载微调器!
我们可以看到有时label
阻碍了我们的loading spinner
,让我们:
*加载微调器不活动时显示
*激活微调器时隐藏
在loadingSpinner.startAnimation(self.view)
之后添加staticLabel.isHidden = true
,这显示在loading spinner
之后。 和self?.staticLabel.isHidden = false
在隐藏self?.loadingSpinner.stopAnimation(self?.view)
之后隐藏了loading spinner
之后。
再次运行该应用程序,您应该会看到一个漂亮的加载微调器。
我们要添加的最后一个UI组件是Alert Box
。 我们将使用NSAlert,这是一个内置的警报表,具有简单的设置,允许我们进行调整并显示一个不错的弹出警报框。
继续并添加此功能,它将显示我们的NSAlert
弹出框:
fileprivate func showSuccessAlert(url:String){
让警报= NSAlert()
alert.messageText =网址
alert.alertStyle = .informational
alert.addButton(withTitle:“复制到剪贴板”)
让响应= alert.runModal()
如果响应== NSAlertFirstButtonReturn {
NSPasteboard.general()。clearContents()
NSPasteboard.general()。setString(url,forType:NSPasteboardTypeString)
}
}
然后,继续并在self?.staticLabel.isHidden = false
之后添加以下代码:
自我?.showSuccessAlert(URL:imgUrl)
现在运行该应用程序并将图像拖入其中,您应该会看到一个漂亮的带有按钮的本机弹出警报框,单击该按钮时,它将URL复制到剪贴板,您可以将其粘贴到任何地方以供个人使用!
那么我们取得了什么成就?
- 我们学习了如何使用Cocoa从零开始完全构建macOS应用。
- 我们学习了如何使用Xcode IB设计UI元素。
- 我们学习了如何创建自定义视图类 。
- 我们学习了如何设计委托模式 。
- 我们学习了如何使用Carthage安装外部库。
- 我们学习了如何使用Alamofire上传图片。
- 我们学习了如何使用uploads.im的开源API。
- 我们学习了如何使用NSAlert 。
通常,这就是创建macOS应用程序的方式,我没有讲过很多内容,这取决于您进一步探索并制作有意义和有用的产品,供世界各地的人们使用!
如果您对本教程有任何疑问,请在下面留下您的评论,并让我知道。
对于示例项目,您可以在GitHub上下载完整的源代码。