原文:http://www.raywenderlich.com/87002/getting-started-with-os-x-and-swift-tutorial-part-1
翻译原文:http://blog.csdn.net/kmyhy/article/details/45150649
打开Xcode,使用 File\NewProject… 菜单,在弹出窗口中选择 “OS X/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 asNSView).bounds |
在 OS X中,窗口(NSWindow对象)总是有一个默认的View,即contentView。它自动占据整个窗口的大小。当我们想在窗口中使用自己的视图时,需要用addSubview方法将它添加到contentView的subviews中。
在iOS开发中,我们可以设置将一个View Controller直接设置为窗口的rootViewController属性,但在OS X中你只能将视图添加到contentView的subviews,因为OS X中没有rootViewController的概念。
运行App,你将看到如下画面:
数据模型
接下来创建数据模型。
首先我们来熟悉一下Xcode项目文件的组织结构:
默认模板会创建一个以项目名称为名的文件夹。在这个文件夹下有一个supporting files的子文件夹,其中存放plist和资源文件。当项目很大时,会创建大量的文件,查找文件就会变得很困难。因此我们需要有一个良好的项目文件组织形式。
首先,我们新建一个文件夹(group),命名为GUI。在ScaryBugsMac文件夹上点击右键,将弹出一个快捷菜单,选择NewGroup,然后输入GUI。
然后将所有跟UI有关的文件拖到这个文件夹( AppDelegate.swift, MasterViewController.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中显示这些图片和数据。
显示昆虫列表
在 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) asNSTableCellView // 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 文件夹中:
创建详情页面
在iOS中,典型的“主-细页面App”需要创建两个视图,但在 OS X,由于屏幕不再受到限制,我们可以将它们合并在同一个视图中。
打开MasterViewController.xib,选中view,将宽度和高度拖大。如图:
我们需要显示下列信息: 昆虫名, 惊悚指数和昆虫图片。
昆虫名用NSTextField 控件显示,惊悚指数用EDStarRating 控件显示,昆虫图片则用NSImageView显示。
此外,我们还需要两个Label,用于表示每个字段的意义(标题)。
拖一个 Text Field (昆虫名), 2个Labels (字段标题), 一个Image View 到view中。
EDStarRating 控件是一个定制控件,无法在Objects Library中找到它,因此你需要先拖入一个 “Custom View”控件。
将这些控件放到view的右边,从上到下依次摆放:
· 首先是一个Label,用于充当昆虫名的字段标题,在它下边是 textfield。
· 在text field下面是第二个 label(惊悚指数的字段标题)。
· 在这个label,下边是一个customview (后面将改成EDStarRating控件)。
· 最下面是image view below 控件。
所有控件左对齐,如下图所示:
然后选中custom view 控件,打开Identity面板(第三个标签按钮)将Class 修改为EDStarRating。
选择第一个label,打开Attributes 面板(第4个标签按钮),修改Title 为 “名称”.
依照上面的方法,将第二个label的title 改为“Rating”。
选择最顶级的 view (在document outline面板中显示为“Custom View”) ,打开Size 面板,查看它的大小:
打开 MainMenu.xib, 选择 ScaryBugsMac window, 设置window 的宽高为前面记住的宽高。然后勾选MinimumSize 。
运行后效果如下:
EDStarRating控件并没有在界面上显示,这是因为我们还没有配置它。
打开 MasterViewController.xib,打开Assistant Editor (工具栏中“Editor” 面板的第二个按钮), 并确保当前编辑的内容是MasterViewController.swift。
选中table View,按下右键,拖一条线到MasterViewController.swift文件中:
这将弹出一个窗口,允许你创建一个IBOutlet。在Name中输入bugsTableView, Storage 设置为 Weak, 然后点击Connect。
重复上述步骤,为text field和image view创建两个IBOutlet:
bugTitleView、bugImageView。
对于custom view, 则创建一个IBOutlet: bugRating.
最终, MasterViewController.swift文件中将新增如下内容:
@IBOutlet weak var bugsTableView: NSTableView! @IBOutlet weak var bugTitleView: NSTextField! @IBOutlet weak var bugImageView: NSImageView! @IBOutlet weak var bugRating: EDStarRating! |
显示昆虫详情
打开MasterViewController.swift 增加如下方法:
func selectedBugDoc() -> ScaryBugDoc? { let selectedRow = self.bugsTableView.selectedRow; if selectedRow >= 0 && selectedRow < self.bugs.count { return self.bugs[selectedRow] } return nil } 这个方法根据用户选中的行索引,从数据模型中检索响应的对象。 |
然后是这个方法:
func updateDetailInfo(doc: ScaryBugDoc?) { var title = "" var image: NSImage? var rating = 0.0 if let scaryBugDoc = doc { title = scaryBugDoc.data.title image = scaryBugDoc.fullImage rating = scaryBugDoc.data.rating } self.bugTitleView.stringValue = title self.bugImageView.image = image self.bugRating.rating = Float(rating) } |
这个方法根据ScaryBugDoc对象,将昆虫的信息和图片在UI上显示。然后是这个方法:
func tableViewSelectionDidChange(notification: NSNotification!) { let selectedDoc = selectedBugDoc() updateDetailInfo(selectedDoc) } |
当用户改变了在表格中的选择时,这个方法调用前两个实用方法。
从OS X 10.10 Yosemite开始,View Controller 使用了新的
viewWillAppear, viewDidLoad,以及其它iOS风格的生命周期方法。而在OS X中传统的创建视图方法一般是 loadView(), 这个方法是向后兼容的,因此我们使用这个方法:
override func loadView() { super.loadView() self.bugRating.starImage = NSImage(named: "star.png") self.bugRating.starHighlightedImage = NSImage(named:"shockedface2_full.png") self.bugRating.starImage = NSImage(named:"shockedface2_empty.png") self.bugRating.delegate = self self.bugRating.maxRating = 5 self.bugRating.horizontalMargin = 12 self.bugRating.editable = true self.bugRating.displayMode = UInt(EDStarRatingDisplayFull) self.bugRating.rating = Float(0.0) } |
在这里,我们初始化EDStarRating控件:用于表示昆虫惊悚指数的图片,控件的delegate属性以及其它参数。
然后在MasterViewController.swift 最后增加一个extension声明:
// MARK: - EDStarRatingProtocol extension MasterViewController: EDStarRatingProtocol { } |
等下在来实现这个EDStarRatingProtocol 协议。
先编译运行程序,效果如下:
添加、删除
打开MasterViewController.xib ,拖两个“Gradient Button” 到 table view下。 选择其中一个按钮, 打开 Attributes 面板,删除Title属性中的内容,然后在Image属性选择,这将使按钮显示为一个“+”号。
同样,将另一个按钮设置为“-”号按钮(Image属性选择为 “NSRemoveTemplate”)。
打开Assistant Editor 窗口,确保当前内容为MasterViewController.swift文件,首先添加一个扩展的定义:
// MARK: - IBActions extension MasterViewController { } |
严格来说这个扩展并非必须,但通过这种方式,我们能更好地组织我们的Swift代码。然后选择加号按钮,右键拖一条线到这个扩展上。
在弹出的窗口中,Connection一栏选择Action,Name一栏输入 addBug, 然后点击Connect.
![](http://b.picphotos.baidu.com/album/s%3D550%3Bq%3D90%3Bc%3Dxiangce%2C100%2C100/sign=0f7cdb17d409b3deefbfe46dfc841dbc/9358d109b3de9c82886a85f06981800a19d84345.jpg?referer=9acfb54c9745d688fa158794ad5b&x=.jpg)
这样将创建一个 addBug(_:) 方法,每当加号按钮被点击,系统将调用这个方法。在减号按钮上重复同样步骤, Name请使用 deleteBug.
打开 MasterViewController.swift实现addBug方法如下:
// 1. 使用默认值创建一个新的ScaryBugDoc实例 let newDoc = ScaryBugDoc(title: "New Bug", rating: 0.0, thumbImage: nil, fullImage: nil) // 2. 将该实例添加到model 数组 self.bugs.append(newDoc) let newRowIndex = self.bugs.count - 1 // 3.向table view插入新行 self.bugsTableView.insertRowsAtIndexes(NSIndexSet(index: newRowIndex), withAnimation: NSTableViewAnimationOptions.EffectGap) // 4. 选中并滚动到新行 self.bugsTableView.selectRowIndexes(NSIndexSet(index: newRowIndex), byExtendingSelection:false) self.bugsTableView.scrollRowToVisible(newRowIndex) |
实现deleteBug()方法如下:
// 1. Get selected doc if let selectedDoc = selectedBugDoc() { // 2. Remove the bug from the model self.bugs.removeAtIndex(self.bugsTableView.selectedRow) // 3. Remove the selected row from the table view self.bugsTableView.removeRowsAtIndexes( NSIndexSet(index:self.bugsTableView.selectedRow), withAnimation: NSTableViewAnimationOptions.SlideRight) // 4. Clear detail info updateDetailInfo(nil) } |
编辑
打开 MasterViewController.xib, 打开 Assistant Editor, 确保当前显示的文件是 MasterViewController.swift。
选中text field, 右键拖到 MasterViewController.swift 文件中的addBug()方法之前:
这将允许你为Text Field创建一个IBAction,Name 请使用bugTitleDidEndEdit。
这个方法将在text field结束编辑时调用(当用户按下回车键或者离开Text Field控件)。
回到MasterViewController.swift, 添加方法:
func reloadSelectedBugRow() { let indexSet = NSIndexSet(index: self.bugsTableView.selectedRow) let columnSet = NSIndexSet(index: 0) self.bugsTableView.reloadDataForRowIndexes(indexSet, columnIndexes:columnSet) } |
在这个方法中,我们重新加载该行数据模型,你需要在模型数据被改动后调用这个方法。
bugTitleDidEndEdit 方法实现如下:
if let selectedDoc = selectedBugDoc() { selectedDoc.data.title = self.bugTitleView.stringValue reloadSelectedBugRow() } |
首先,调用selectedBugDoc()获得相关昆虫的信息,然后从text field读取文本字符串,并用它来更新模型中的昆虫名称。最后调用reloadSelectedBugRow()通知单元格进行刷新。
注意: 通知table view自己刷新cell要比直接操纵cell的内容要好。
运行App,从列表选中某个昆虫,尝试修改其名称(记得按回车键),表格中的昆虫名将随之改变!
但是如果你切换到其他昆虫,然后返回修改的那一个昆虫,你会发现数据又回到原来(未改动前)了。这是因为我们没有将模型对象进行持久化(保存进文件)。
接下来实现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按钮,所有数据又恢复原样。
缩放
打开MasterViewController.xib,在Size面板中查看 Custome View的大小。在本例中,它应该是540x400大小。但是读者的这个数字会有不同。不管是多大,请记下这个数字。待会会用到。
这将是App出口的最小大小。打开 MainMenu.xib, 选择 window 对象。在Size 面板中,勾上Constraint右边的Minimum Size 选项,然后将width 和 height 修改为同样的值。
运行程序。
改变出口的大小,这次当窗口缩小到最小尺寸后,就无法再缩小。
接下来我们需要解决控件自适应大小的问题,包括TableView和细节页面中的控件。
首先是MasterViewController视图。
打开 AppDelegate.swift, 在applicationDidFinishLaunching()方法最后加入:
// 3. 设置 masterViewController.view的布局约束 masterViewController.view.translatesAutoresizingMaskIntoConstraints = false let verticalConstraints = NSLayoutConstraint.constraintsWithVisualFormat("V:|[subView]|", options: NSLayoutFormatOptions(0), metrics: nil, views: ["subView" : masterViewController.view]) let horizontalConstraints = NSLayoutConstraint.constraintsWithVisualFormat("H:|[subView]|", options: NSLayoutFormatOptions(0), metrics: nil, views: ["subView" : masterViewController.view]) NSLayoutConstraint.activateConstraints(verticalConstraints + horizontalConstraints) |
在这里,我们允许MasterViewController在宽、高两个维度上使用自动布局。
接下来,我们使用IB的自动布局来约束来对table view进行布局,以便在窗口大小改变时,让它的高度自动增长,但宽度保持恒定。
打开 MasterViewController.xib ,选择table view,点击右下角的Pin 按钮, 勾上上、左、下3个约束,以及一个等宽约束,然后点击 “Add 4 Constraints”:
注意图片中的约束值可能和读者的实际值有所不同。
然后选择Reset按钮,设置其与Table View的上边距约束和一个相对于Main View的左边距约束:
![](http://a.picphotos.baidu.com/album/s%3D550%3Bq%3D90%3Bc%3Dxiangce%2C100%2C100/sign=0620620abc014a90853e46b8994c482f/8b82b9014a90f6036bba41ff3c12b31bb051ed04.jpg?referer=b1db629b662762d0d929918f4b1a&x=.jpg)
接着选择分隔线,设置其与Main View的上、下边距约束,以及与TableView的左边距约束,确认在左边距约束的下拉列表中选择了 “Bordered ScrollView – Table View” :
![](http://d.picphotos.baidu.com/album/s%3D550%3Bq%3D90%3Bc%3Dxiangce%2C100%2C100/sign=c09445272bdda3cc0fe4b82531d2483c/adaf2edda3cc7cd906b2e8613c01213fb90e91ef.jpg?referer=d57d6439ce1349542709dc54dfa5&x=.jpg)
接下来设置“Add” 和 “Delete” 按钮。我们不需要改变它们的大小,唯一需要改变的是它们和Table View之间的距离。对于两个按钮,我们需要设置它们的左、上,宽度和高度约束。已Add按钮为例,显示如下图:
![](http://e.picphotos.baidu.com/album/s%3D550%3Bq%3D90%3Bc%3Dxiangce%2C100%2C100/sign=20b33bd0c9bf6c81f3372ced8c05c008/d058ccbf6c81800aa5e956b6b43533fa828b477e.jpg?referer=680e6d1ca1c27d1efc310ef4a114&x=.jpg)
Delete按钮类似。
运行程序,改变窗口大小,查看效果。
然后是右边的细节页面。在这个页面中,当窗口宽度变大时,所有控件的宽度也会变大。以TableView相同的方法,分别设置它们的自动布局如下:
· 设置Name标签的左、上约束。
· 设置bugTitleView
的左、上约束。
· 设置Rating标签的左、上约束。
· 设置 bugRating
的左、上、右和高约束。
· 设置bugImageView
的左、上、下、右约束。
· 移动 Change Picture 按钮,以便它的右边沿刚好和bugImageView
的右边沿平齐,然后设置它的右、下约束。
设置完bugImageView
按钮可能会出现几个警告,但ChangePicture 按钮之后,这些警告会消除。
上述步骤做完后,故事板将如下图所示:
![](http://a.picphotos.baidu.com/album/s%3D550%3Bq%3D90%3Bc%3Dxiangce%2C100%2C100/sign=d94157b6b43533faf1b6932b98e88c22/bd3eb13533fa828b6687435bf81f4134960a5ae6.jpg?referer=ce6df05da86eddc47ff080cba6bc&x=.jpg)
编译运行,再次缩放窗口。
![](http://g.picphotos.baidu.com/album/s%3D550%3Bq%3D90%3Bc%3Dxiangce%2C100%2C100/sign=8dbb5e999613b07eb9bd500d3cece01e/b3119313b07eca801371e05f942397dda1448316.jpg?referer=008f4b67cb1b9d16d3d0af51ed0c&x=.jpg)
bugImageView
的Scale设置会对图片产生不同的显示效果。在IB中选择Image Well 控件, 修改其scaling属性为“Proportionally Up or Down” 或者 “Axes Independently”,然后运行App,看看有什么不同。
注意: 如果你想限制窗口的最大缩放尺寸,则你也可以用设置窗口最小缩放尺寸同样的方式加以限制。
关注细节
关于用户体验方面,我们仍然有一些细节值得注意。例如:运行App,不要选择任何昆虫,点击“Delete” 或者 “Change Picture” 按钮,什么都不会发生,Why?
作为程序员,你当然知道当用户什么都没选择的情况下,不应当执行任何操作,但对于用户而言,这种情况仍然显得不太友好:
我们通过以下方式来解决这个问题:
· 如果用户选中了某个单元格,我们才让Delete按钮、Change picture按钮、文本框和rating view可用。
· 如果用户未选择任何行,我们禁用上述控件,用户将不能和它们进行任何交互。
打开MasterViewController.xib,选择Delete按钮,在属性面板,将Enabled属性前的勾去掉。
![](http://h.picphotos.baidu.com/album/s%3D550%3Bq%3D90%3Bc%3Dxiangce%2C100%2C100/sign=6125a8b45643fbf2c12ca6268045bbbd/11385343fbf2b211346a08f2cf8065380dd78ec2.jpg?referer=42a45f9d95ef76c689c5cf1beac0&x=.jpg)
在ChangePicture 按钮、text field上重复上述步骤。
这样,当程序刚启动时,上述控件将被禁用。然后我们需要在用户选择了表格中的单元格之后再启用它们。要实现这个目的,我们首先需要为它们创建IBOutlet。
打开AssistantEditor 确保当前编辑的文件为MasterViewController.swift。
选择“Delete” 按钮,右键将它拖动到 MasterViewController.swift文件中。
![](http://e.picphotos.baidu.com/album/s%3D550%3Bq%3D90%3Bc%3Dxiangce%2C100%2C100/sign=53a8c7470f46f21fcd345e56c61f1a5d/7acb0a46f21fbe09ef014aa86e600c338744ad2b.jpg?referer=88b52daf92dda144831e58820be9&x=.jpg)
在弹出的出口中,选择connection为“Outlet”, name 栏输入 deleteButton,然后点击Connect.
![](http://b.picphotos.baidu.com/album/s%3D550%3Bq%3D90%3Bc%3Dxiangce%2C100%2C100/sign=f10f9ba1d12a283447a6360e6b8eb8d7/242dd42a2834349b926de539ccea15ce36d3be27.jpg?referer=329ac255881001e9172b203ffafd&x=.jpg)
重复上述步骤,为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()方法——只要用户进行过新建、删除和编辑操作。