app swift 开发mac

2015-04-20 15:58:01 kmyhy 阅读数 3772

数据模型

接下来创建数据模型。

首先我们来熟悉一下Xcode项目文件的组织结构:


默认模板会创建一个以项目名称为名的文件夹。在这个文件夹下有一个supporting files的子文件夹,其中存放plist和资源文件。当项目很大时,会创建大量的文件,查找文件就会变得很困难。因此我们需要有一个良好的项目文件组织形式。

首先,我们新建一个文件夹(group),命名为GUI。在ScaryBugsMac文件夹上点击右键,将弹出一个快捷菜单,选择NewGroup,然后输入GUI。

然后将所有跟UI有关的文件拖到这个文件夹( AppDelegate.swiftMasterViewController.swift/.xiband MainMenu.xib),如下图所示:


然后新建另一个文件夹Model。


在Model文件夹中将包含如下内容

·      ScaryBugData: 包含两个属性:昆虫的名称及昆虫的估价。

·      ScaryBugDoc: 包含3个属性:昆虫图片、昆虫缩略图及一个ScaryBugData属性。

实现模型对象

注意: 如果你阅读过 How ToCreate A Simple iPhone App on iOS 5 Tutorial, 你会发现接下来的内容和那篇教程中的相应内容几乎一模一样。这是因为Mac和iOS编程大部分SDK都是系统的,除了UI和操作系统相关的API。而模型对象不涉及UI,因此模型对象的代码基本是一致的。

对于ScaryBug的模型类,将Mac版本与iOS版本只有一个地方不同,即将UIImage类修改为NSImage即可。当然,你也需要将它从O-C实现修改为Swift实现。

在Model文件夹上点击右键,点击 “New File…”,然后选择OS X\Source\Cocoa Class 模板,然后点击Next。


类名输入 ScaryBugData, Subclass of 输入 NSObject ,点击 Next。


在最后一个弹出界面中,点击Create。项目导航窗口将显示如下:


打开ScaryBugData.swift 替换为如下内容:

import Foundation  

class ScaryBugData: NSObject {

   var title: String

   var rating: Double  

   override init() {

    self.title = String()

    self.rating = 0.0  

}

init(title: String, rating: Double) {

     self.title = title

     self.rating = rating  

}

}

然后创建另一个模型对象ScaryBugDoc

打开ScaryBugDoc.swift 编辑为如下内容:

import Foundation

import AppKit  

class ScaryBugDoc: NSObject {

   var data: ScaryBugData

   var thumbImage: NSImage?

   var fullImage: NSImage?

override init() {

     self.data = ScaryBugData()

   }

init(title: String, rating: Double, thumbImage: NSImage?, fullImage: NSImage?) {

     self.data = ScaryBugData(title: title, rating: rating)

     self.thumbImage = thumbImage

     self.fullImage = fullImage 

 }

}

注意thumbImage 和fullImage声明为可空的 NSImage ,因此他们不需要在默认构造函数中初始化。

打开MasterViewController.swift ,增加一个属性声明:

var bugs = [ScaryBugDoc]()

这个数组属性用于存储昆虫列表,接下来我们将会用一些数据填充这个数组。

填充数据及图片

MasterViewController 需要用一系列昆虫来填充。你可以从此处下载所需的

 昆虫图片

下载完图片之后,,将所有图片从Finder中拖到Images.xcassets中如下图右边AppIcon之下的位置:


打开 MasterViewController.swift 添加如下方法:

func setupSampleBugs() {

   let bug1 = ScaryBugDoc(title: "Potato Bug", rating: 4.0,

     thumbImage:NSImage(named: "potatoBugThumb"), fullImage: NSImage(named: "potatoBug"))

   let bug2 = ScaryBugDoc(title: "House Centipede", rating: 3.0,

thumbImage:NSImage(named: "centipedeThumb"), fullImage: NSImage(named: "centipede"))  

   let bug3 = ScaryBugDoc(title: "Wolf Spider", rating: 5.0,

thumbImage:NSImage(named: "wolfSpiderThumb"), fullImage: NSImage(named: "wolfSpider"))  

   let bug4 = ScaryBugDoc(title: "Lady Bug", rating: 1.0,

thumbImage:NSImage(named: "ladybugThumb"), fullImage: NSImage(named: "ladybug"))

   bugs = [bug1, bug2, bug3, bug4]

}

打开AppDelegate.swift ,找到 applicationDidFinishLaunching方法,在addSubview之前加入以下代码:

masterViewController.setupSampleBugs()

编译运行程序,确保编译通过。

接下来,我们将在UI中显示这些图片和数据。


2015-05-15 21:23:14 kmyhy 阅读数 2828

关注细节

关于用户体验方面,我们仍然有一些细节值得注意。例如:运行App,不要选择任何昆虫,点击“Delete” 或者 “Change Picture” 按钮,什么都不会发生,Why?

作为程序员,你当然知道当用户什么都没选择的情况下,不应当执行任何操作,但对于用户而言,这种情况仍然显得不太友好:


我们通过以下方式来解决这个问题:

·      如果用户选中了某个单元格,我们才让Delete按钮、Change picture按钮、文本框和rating view可用。

·      如果用户未选择任何行,我们禁用上述控件,用户将不能和它们进行任何交互。

打开MasterViewController.xib,选择Delete按钮,在属性面板,将Enabled属性前的勾去掉。


在ChangePicture 按钮、text field上重复上述步骤。

这样,当程序刚启动时,上述控件将被禁用。然后我们需要在用户选择了表格中的单元格之后再启用它们。要实现这个目的,我们首先需要为它们创建IBOutlet。

打开AssistantEditor 确保当前编辑的文件为MasterViewController.swift

选择“Delete” 按钮,右键将它拖动到 MasterViewController.swift文件中。


在弹出的出口中,选择connection为“Outlet”, name 栏输入 deleteButton,然后点击Connect.


重复上述步骤,为Changepicture按钮创建一个IBOutlet,名为changePictureButton.

打开MasterViewController.swift, 在tableViewSelectionDidChange(_:),加入以下代码,位于 updateDetailInfo(selectedDoc)一行以后:

// Enable/disable buttons based on the selection 
let buttonsEnabled = (selectedDoc != nil) 
deleteButton.enabled = buttonsEnabled 
changePictureButton.enabled = buttonsEnabled 
bugRating.editable = buttonsEnabled 
bugTitleView.enabled = buttonsEnabled

我们首先判断控件是否需要被启用,这是通过用户是否选中单元格来决定的。如果selectedDoc为空,则意味着没有行被选中,这说明控件应当被禁用,否则启用控件。

此外,ratingview 默认是启用的,所以我们还需要在

 loadView() 中禁用它。找到这行语句:

self.bugRating.editable = true

修改为

self.bugRating.editable = false

运行程序。

注意: 你还可以在用户未选择有效行时讲整个细节页面都隐藏起来,但这完全取决于你。

保存数据

就像iOS,Mac App也能够使用 NSUserDefaults, 因此我们完全可以把数据存放到那里。

首先我们必须让模型类实现NSCoding 协议。在ScaryBugData.swift 中定义一个扩展:

// MARK: - NSCoding   
extension ScaryBugData: NSCoding {
   func encodeWithCoder(coder: NSCoder) {
coder.encodeObject(self.title, forKey: "title")
coder.encodeObject(Double(self.rating), forKey: "rating")
   } 
}

首先我们让ScaryBugData实现NSCoding协议中的 encodeWithCoder方法。这个方法用于对自定义类进行编码。

同时还需要一个与之对应的初始化方法。不同的是,我们无法在扩展中定义required的init方法,因此必须把它定义在类代码中:

required convenience init(coder decoder: NSCoder) {
   self.init() 
   self.title = decoder.decodeObjectForKey("title") as String
   self.rating = decoder.decodeObjectForKey("rating") as Double 
} 

init(coder:) 方法和encodeWithCoder方法向反,用于从文件中读取数据并反编码为对象。

然后在ScaryBugDoc.swift 中定义一个扩展实现 NSCoding 协议:

// MARK: - NSCoding 
extension ScaryBugDoc: NSCoding {
   func encodeWithCoder(coder: NSCoder) {
coder.encodeObject(self.data, forKey: "data")
coder.encodeObject(self.thumbImage, forKey: "thumbImage") 
coder.encodeObject(self.fullImage, forKey: "fullImage")
} 
}

然后在类代码中(不要在扩展定义中)定义Init方法:

required convenience init(coder decoder: NSCoder) {
   self.init()
   self.data = decoder.decodeObjectForKey("data") as ScaryBugData
   self.thumbImage = decoder.decodeObjectForKey("thumbImage") as NSImage?
   self.fullImage = decoder.decodeObjectForKey("fullImage") as NSImage? 
}

接下来将模型数据保存到NSUserDefaults. 在 MasterViewController.swift中添加一个方法:

func saveBugs() {
   let data = NSKeyedArchiver.archivedDataWithRootObject(self.bugs)
   NSUserDefaults.standardUserDefaults().setObject(data, forKey: "bugs")
   NSUserDefaults.standardUserDefaults().synchronize() 
}
这个方法首先将bugs数组构建为一个NSData对象,然后保存到

NSUserDefaults.NSKeyedArchiver。当然数组中的所有对象都实现了 NSCoding.

打开 AppDelegate.swift, 在applicationWillTerminate()中加入:

masterViewController.saveBugs()

这样,在App退出之前,将所有昆虫数据保存到了 NSUserDefaults.

加载数据

AppDelegate.swift, 找到applicationDidFinishLaunching 的

masterViewController.setupSampleBugs()

替换为

if let data = NSUserDefaults.standardUserDefaults().objectForKey("bugs") as? NSData {
   masterViewController.bugs = NSKeyedUnarchiver.unarchiveObjectWithData(data) as [ScaryBugDoc] 
} else {
   masterViewController.setupSampleBugs() 
}

运行程序,添加、删除和编辑昆虫数据,然后退出程序。重新启动App之后,所有上次进行的修改都被保留住了。

注意: 如果应用程序不是正常的退出,则saveBugs() 方法不会调用 — 请用Command-Q 退出程序,而不是从Xcode中终止程序。要解决这个问题,你可以在MasterViewController的某个恰当的时机调用saveBug()方法——只要用户进行过新建、删除和编辑操作。

 



2015-04-20 15:27:48 kmyhy 阅读数 14179

原文超级详细(图文),小白表示毫无压力:http://www.raywenderlich.com/87002/getting-started-with-os-x-and-swift-tutorial-part-1

由于原文浅显到略显啰嗦,因此翻译时我有选择地删除了部分段落。

打开Xcode,使用 File\NewProject… 菜单,在弹出窗口中选择 “macOs/Coaca Application”,然后Next。


在接下来的窗口中,配置App信息。在product name栏中输入ScaryBugsMac,输入你的机构名以及机构ID。剩余字段保留为空白。

选择Swift作为开发语言,保持所有选项框反选,document extension栏保留为空白。然后点Next。


然后Xcode会要求你选择项目保存路径。选择一个物理路径,然后点击Create。

项目就创建完了,这是一个单窗口App。点击工具栏左上角的Run按钮,运行这个程序,效果如下图所示。


首先我们来总结一下。我们使用Xcode模板创建了一个Mac App项目,然后编译运行了这个空白项目。与iOS开发的最大不同在于:

·      窗口不需要特别指明大小,比如iPhone或iPad屏幕大小——MacApp的窗口是可以通过拖动来改变大小的。

·      Map App可以拥有多个窗口,窗口支持最小化,重排等操作。

然后我们来新建一个View Controller,并在它上面放入App的主界面。使用

 File\New\File… 菜单,在弹出窗口中,选择 OS X\Source\Cocoa Class,然后点Next。


类名填入 MasterViewController, “Subclass of”填入NSViewController。确保“Also create XIB file for user interface” 为勾选,然后点Next。


在最后一个弹出窗口中,点击Create。新的View Controller将显示在项目导航窗口中:


打开MasterViewController.xib。需要注意的是,在Mac App中,有大量的类和iOS中都类似,只不过是以NS前缀命名。例如NSScrollView、NSLabel、NSButton等。

在右下角的UI Controls面板(位于第三个Tab)中,选中NSTableView将它拖到MasterViewController.xib的画布中。


不要担心Table View的大小,我们待会会来处理它。

打开 AppDelegate.swif在window属性下面插入如下语句:

var masterViewController: MasterViewController!

 

找到 applicationDidFinishLaunching 方法,这个方法在App启动时调用。

注意: 这个方法等同于iOS中的application(_:didFinishLaunchingWithOptions:)方法。

applicationDidFinishLaunching方法内,加入以下语句:

masterViewController = MasterViewController(nibName: "MasterViewController", bundle: nil)

window.contentView.addSubview(masterViewController.view)

masterViewController.view.frame = (window.contentView as NSView).bounds

 

在 OS X中,窗口(NSWindow对象)总是有一个默认的View,即contentView。它自动占据整个窗口的大小。当我们想在窗口中使用自己的视图时,需要用addSubview方法将它添加到contentView的subviews中。

在iOS开发中,我们可以设置将一个View Controller直接设置为窗口的rootViewController属性,但在OS X中你只能将视图添加到contentView的subviews,因为OS X中没有rootViewController的概念。

运行App,你将看到如下画面:




2015-05-07 09:28:46 kmyhy 阅读数 1990

接下来实现EDStarRating 的编辑。 在loadView 方法中,我们已经配置了EDStarRating的delegate属性,我们仅仅需要实现相关委托方法即可。

打开MasterViewController.swift 在 EDStarRatingProtocol 扩展中添加如下方法:

func starsSelectionChanged(control: EDStarRating!, rating: Float) {

   if let selectedDoc = selectedBugDoc() {

selectedDoc.data.rating = Double(self.bugRating.rating)

   }

}

跟前面几乎一样: 获得用户选定的昆虫模型,用修改后的值赋值给它。

运行程序。需要注意的是,用户设定新的评级后这个值是被持久化的,哪怕你切换到其他昆虫然后有切换回来。


获取本地图片

打开 MasterViewController.xib,拖一个“Push Button” 控件到image view下方。

修改按钮的title 为 “Change Picture”:


如同加号按钮和减号按钮,为Change Picture 按钮创建一个IBAction,命名为 changePicture

这个action在按钮点击时调用。

OS X 有一个特有的控件叫做 IKPictureTaker,允许用户从计算机上选择一张图片,或者从摄像头捕捉一张图片。

当用户选择了图片之后,这个控件会调用指定的delegate方法。

打开MasterViewController.swift 加入以下import 语句:

import Quartz

这个 image picker属于 Quartz 框架。

changePicture方法中,添加代码:

if let selectedDoc = selectedBugDoc() {

   IKPictureTaker().beginPictureTakerSheetForWindow(self.view.window,

withDelegate: self,

     didEndSelector: "pictureTakerDidEnd:returnCode:contextInfo:",

     contextInfo: nil)

}

我们先检查用户是否选择了有效的昆虫,如果是,显示picture taker控件。

然后实现pictureTakerDidEnd(_:returnCode:contextInfo:)方法:

func pictureTakerDidEnd(picker: IKPictureTaker, returnCode: NSInteger, contextInfo: UnsafePointer<Void>) {

   let image = picker.outputImage()

   if image != nil && returnCode == NSOKButton {

self.bugImageView.image = image

if let selectedDoc = selectedBugDoc() {

       selectedDoc.fullImage = image

       selectedDoc.thumbImage = image.imageByScalingAndCroppingForSize(CGSize(width: 44, height: 44))       reloadSelectedBugRow()

}  

   }

}

首先检查用户是否点击了OK (NSOKButton) 以及选择的图片是否有效。

如果是,获取用户选定的昆虫模型,修改昆虫的图片及缩略图,然后更新cell。

运行程序,选择一个昆虫,点击Change Picture, 从本地文件或摄像头中获取一张图片,这张图片将立即在选定的cell中得到更新。

一些细节上的问题

当你运行程序,视图改变窗口大小,你会发现控件并不能自动适应大小。


这是窗口拖大后的效果。


pplns:o="urn:schemas-microsoft-com:office:office"xmlns:w="urn:schemas-microsoft-com:office:word"xmlns:m="http://schemas.microsoft.com/office/2004/12/omml"xmlns="http://www.w3.org/TR/REC-html40">

这是窗口缩小后的效果。

另外,我们还没有为App数据进行持久化。一旦App重启,用户对数据进行的增加和修改都会丢失。

打开MasterViewController.xib,将View的Size缩小至最小能够足以显示所有控件的程度。


在上图中,3个按钮放在了同一排。在右边细节展示区域中,所有的控件都左对齐,且宽度一致(除了ChangePicture按钮)。

然后,我们在中间增加一个分割线。拖一个 VerticalLine 到View的中央。


复原操作

复原操作用于将数据恢复至原来的状态。拖一个Push 按钮在Table View下方,修改其标题为Reset。然后打开Assistant Editor,为按钮创建一个IBAction,名为resetData(确认当前打开的源文件为MasterViewController.swift )。


resetData()方法加入如下代码:

setupSampleBugs() 
updateDetailInfo(nil) 
bugsTableView.reloadData()

setupSampleBugs() 方法调用会恢复所有模型数据。 以nil作为参数值调用updateDetailInfo 方法将清除所有细节字段。然后刷新Table View。

运行程序,添加、删除或修改任意数据。然后点击Reset按钮,所有数据又恢复原样。


2015-04-22 14:00:23 kmyhy 阅读数 3256

显示昆虫列表

在 OS X中,Table View使用 NSTableView类,它等同于iOS的UITableView 类,但有一个最大的不同是:NSTableView 的每一行有多个列或多个单元格。

·      OS X 10.7Lion之前,table view cell继承于NSCell类。而后者并非NSView类,因此开发者需要自己处理绘图和鼠标事件。

·       OS X 10.7开始,table view从 NSView继承。这样就和UITableView差不多了。cell也有相应的View类型,因此也和iOS中的类似——这样我们就轻松得多了!

在本教程中,使用的是基于View的TableView。如果你想了解NSTableView的用法,你可以阅读 这里, 它对 table views 的用法进行了详细的说明。

打开MasterViewController.xib ,选中table view。注意Table View位于Scroll View中的Clip View中,因此第一个点击你选中的会是ScrollView,第二次点击你选中的才是ClipView,第三次点击才会选中Table View。

当然,你也可以直接从IB的Objects面板中选择Table View对象(展开 Clip View对象)。

选中Table View之后,在属性面板中,确认Content Mode一项是设置为View Based而不是Cell Based。同时,因为我们的列表仅显示单列,所以将Columns属性修改为1。

勾选 “Alternating Rows”属性,让表格以“明暗颜色交替”的方式绘制单元格。

反选 “Headers” 属性,因为我们不需要在表格上方显示一个标题。

接下来我们修改单元格的大小。选择Table View上的列,拖动它的大小使其占据整个表格宽度。


然后是单元格的配置。我们需要在单元格中显示昆虫的图片和名称,因此需要在Cell中添加一个Image和一个文本控件。

IB中有一种带Image View和Text Field的NSTableCellView对象,我们可以使用它。

在Object library 面板中,找到 “Image & Text Table Cell View”, 将它拖到Table View中。


在Table View中,将原来的cell删除(用delete键)。

选中Table View Cell,在Size面板中,将高度调整为32。

然后选中Image View和 Text Field,使它们位于单元格中心,并调整ImageView和Text Field的大小,使它们看起来如下图所示:


接下来要为每一列设置一个id。当然对于本教程来说,我们只有一个列,因此列id可能不是必须的。

在Objects面板中选择表格列,打开Identity面板,将Identifier设置为BugColumn。


如同在iOS中一样,Table View也有Data Source和Delegate属性。正常情况下,这两个属性都是同一个对象,即 MasterViewController

选择Table View,打开Connections面板,在Outlets一项下找到delegate和data source。

点击delegate右边的小圆圈,拖到Objects面板中的“File’s Owner”上。


这将吧Table View 的delegate 属性设置为 MasterViewController。重复同样的动作,设置Data Source属性。

最终如下图所示:


打开 MasterViewController.swift 将下列代码放在文件最后:

// MARK: - NSTableViewDataSource

extension MasterViewController: NSTableViewDataSource {

   func numberOfRowsInTableView(aTableView: NSTableView!) -> Int {

     return self.bugs.count  

}  

   func tableView(tableView: NSTableView!, viewForTableColumn tableColumn: NSTableColumn!, row: Int) -> NSView! {

     // 1

var cellView: NSTableCellView = tableView.makeViewWithIdentifier(tableColumn.identifier, owner: self) as NSTableCellView      

// 2    

if tableColumn.identifier == "BugColumn" {

    // 3  

    let bugDoc = self.bugs[row]      

    cellView.imageView!.image = bugDoc.thumbImage

    cellView.textField!.stringValue = bugDoc.data.title

    return cellView    

}      

return cellView  

   }

}  

// MARK: - NSTableViewDelegate

extension MasterViewController: NSTableViewDelegate { }

我们通过扩展让MasterViewController 采用NSTableViewDelegate 和NSTableViewDataSource协议。

要让列表渲染数据至少需要实现两个数据源方法。

首先是numberOfRowsInTableView 方法,OS通过这个方法获取要渲染的表格行数。

其次是tableView(_:viewForTableColumn:row:)方法。OS通过这个方法知道如何去渲染每行中的每个单元格。在这个方法中,我们需要用数据对单元格进行填充。

运行程序,如果一切正常,我们将在表格中看到昆虫列表。

下载资源

为了完成本教程,你可能需要下载这些压缩包,并解压缩。

注意: 为了将昆虫分成 “一点也不可怕” 到 “极度恐怖”几个级别,你还需要用到一个开源的分级组件EDStarRating,这也被包含在压缩包中。

在本教程中,我们不会解释如何实现这个组件,而只是演示如何在项目中使用它。压缩包中还包括了一个NSImage类别,可以从一张大图片生成缩略图。 此外,还包括3张怪脸图片,分别用于显示昆虫的不同级别。

关于 EDStarRating组件,你可以参考它的 github 主页.

首先,在项目导航窗口中创建一个名为Art的文件夹,并将3个怪脸图片拖到这个文件夹中——确保“Copy items if needed” 已勾选, 以及Add to targets中的“ScaryBugsMac” 已选上。

再创建一个名为“Views” 的文件夹, 将EDStarRating.h 和EDStarRating.m拖到该文件夹。 再次确保“Copy items if needed” 已勾选以及Add to targets中的“ScaryBugsMac” 已选上。


点击Finish. 在下一窗口当被问到 “Would you like to configure an Objective-C bridgingheader?” 时选择Yes。这将创建一个Objective-C 类到Swift 代码的桥接头文件。


对于 NSImage+Extras.h 和NSImage+Extras.m,重复上述步骤,只不过这次将它们拖进的是“Helpers”文件夹。

打开ScaryBugsMac-Bridging-Header.h 加入以下import语句:

#import "EDStarRating.h" #import "NSImage+Extras.h"

 

以下为最终效果,其中桥接头文件已经被我们移到 Supporting Files 文件夹中: