使用swift开发OSX应用

swift /OSX

原文:http://www.raywenderlich.com/87002/getting-started-with-os-x-and-swift-tutorial-part-1

翻译原文:http://blog.csdn.net/kmyhy/article/details/45150649

转自:http://blog.csdn.net/u011349387/article/details/50451617

打开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属性下面插入如下语句:

varmasterViewController:MasterViewController!


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

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

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

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

window.contentView.addSubview(masterViewController.view)

masterViewController.view.frame=(window.contentViewasNSView).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/.xibandMainMenu.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替换为如下内容:

importFoundation

classScaryBugData:NSObject{

vartitle:String

varrating:Double

overrideinit(){

self.title=String()

self.rating=0.0

}

init(title:String, rating:Double){

self.title=title

self.rating=rating

}

}

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

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

importFoundation

importAppKit

classScaryBugDoc:NSObject{

vardata:ScaryBugData

varthumbImage:NSImage?

varfullImage:NSImage?

overrideinit(){

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

}

}

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

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

varbugs=[ScaryBugDoc]()

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

填充数据及图片

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

昆虫图片

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

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

funcsetupSampleBugs(){

letbug1=ScaryBugDoc(title:"Potato Bug", rating:4.0,

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

letbug2=ScaryBugDoc(title:"House Centipede", rating:3.0,

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

letbug3=ScaryBugDoc(title:"Wolf Spider", rating:5.0,

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

letbug4=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{

funcnumberOfRowsInTableView(aTableView:NSTableView!)->Int{

returnself.bugs.count

}

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

// 1

varcellView:NSTableCellView=tableView.makeViewWithIdentifier(tableColumn.identifier, owner:self)asNSTableCellView

// 2

iftableColumn.identifier=="BugColumn"{

// 3

letbugDoc=self.bugs[row]

cellView.imageView!.image=bugDoc.thumbImage

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

returncellView

}

returncellView

   }

}

// MARK: - NSTableViewDelegate

extension MasterViewController:NSTableViewDelegate{}

我们通过扩展让MasterViewController采用NSTableViewDelegateNSTableViewDataSource协议。

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

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

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

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

下载资源

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

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

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

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

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

再创建一个名为“Views” 的文件夹, 将EDStarRating.hEDStarRating.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.hNSImage+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:

bugTitleViewbugImageView

对于custom view, 则创建一个IBOutlet:bugRating.

最终,MasterViewController.swift文件中将新增如下内容:

@IBOutlet weakvarbugsTableView:NSTableView!

@IBOutlet weakvarbugTitleView:NSTextField!

@IBOutlet weakvarbugImageView:NSImageView!

@IBOutlet weakvarbugRating:EDStarRating!

显示昆虫详情

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

funcselectedBugDoc()-> ScaryBugDoc?{

letselectedRow=self.bugsTableView.selectedRow;

ifselectedRow >=0&&selectedRow

returnself.bugs[selectedRow]

}

returnnil

}

这个方法根据用户选中的行索引,从数据模型中检索响应的对象。

然后是这个方法:

funcupdateDetailInfo(doc:ScaryBugDoc?){

vartitle=""

varimage:NSImage?

varrating=0.0

ifletscaryBugDoc=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上显示。然后是这个方法:

functableViewSelectionDidChange(notification:NSNotification!){

letselectedDoc=selectedBugDoc()

updateDetailInfo(selectedDoc)

}

当用户改变了在表格中的选择时,这个方法调用前两个实用方法。

从OS X 10.10 Yosemite开始,View Controller 使用了新的

viewWillAppear,viewDidLoad,以及其它iOS风格的生命周期方法。而在OS X中传统的创建视图方法一般是loadView(), 这个方法是向后兼容的,因此我们使用这个方法:

overridefuncloadView(){

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.

这样将创建一个 addBug(_:) 方法,每当加号按钮被点击,系统将调用这个方法。在减号按钮上重复同样步骤, Name请使用deleteBug.

打开MasterViewController.swift实现addBug方法如下:

// 1. 使用默认值创建一个新的ScaryBugDoc实例

letnewDoc=ScaryBugDoc(title:"New Bug", rating:0.0, thumbImage:nil, fullImage:nil)

// 2. 将该实例添加到model 数组

self.bugs.append(newDoc)

letnewRowIndex=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

ifletselectedDoc=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, 添加方法:

funcreloadSelectedBugRow(){

letindexSet=NSIndexSet(index:self.bugsTableView.selectedRow)

letcolumnSet=NSIndexSet(index:0)

self.bugsTableView.reloadDataForRowIndexes(indexSet, columnIndexes:columnSet)

}

在这个方法中,我们重新加载该行数据模型,你需要在模型数据被改动后调用这个方法。

bugTitleDidEndEdit方法实现如下:

ifletselectedDoc=selectedBugDoc(){

selectedDoc.data.title=self.bugTitleView.stringValue

reloadSelectedBugRow()

}

首先,调用selectedBugDoc()获得相关昆虫的信息,然后从text field读取文本字符串,并用它来更新模型中的昆虫名称。最后调用reloadSelectedBugRow()通知单元格进行刷新。

注意:通知table view自己刷新cell要比直接操纵cell的内容要好。

运行App,从列表选中某个昆虫,尝试修改其名称(记得按回车键),表格中的昆虫名将随之改变!

但是如果你切换到其他昆虫,然后返回修改的那一个昆虫,你会发现数据又回到原来(未改动前)了。这是因为我们没有将模型对象进行持久化(保存进文件)。

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

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

funcstarsSelectionChanged(control:EDStarRating!, rating:Float){

ifletselectedDoc=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 语句:

importQuartz

这个 image picker属于 Quartz 框架。

changePicture方法中,添加代码:

ifletselectedDoc=selectedBugDoc(){

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

withDelegate:self,

didEndSelector:"pictureTakerDidEnd:returnCode:contextInfo:",

contextInfo:nil)

}

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

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

funcpictureTakerDidEnd(picker:IKPictureTaker, returnCode:NSInteger, contextInfo:UnsafePointer){

letimage=picker.outputImage()

ifimage!=nil&&returnCode==NSOKButton{

self.bugImageView.image=image

ifletselectedDoc=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

letverticalConstraints=NSLayoutConstraint.constraintsWithVisualFormat("V:|[subView]|",

options:NSLayoutFormatOptions(0),

metrics:nil,

views:["subView":masterViewController.view])

lethorizontalConstraints=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的左边距约束:

接着选择分隔线,设置其与Main View的上、下边距约束,以及与TableView的左边距约束,确认在左边距约束的下拉列表中选择了 “Bordered ScrollView – Table View” :

接下来设置“Add” 和 “Delete” 按钮。我们不需要改变它们的大小,唯一需要改变的是它们和Table View之间的距离。对于两个按钮,我们需要设置它们的左、上,宽度和高度约束。已Add按钮为例,显示如下图:

Delete按钮类似。

运行程序,改变窗口大小,查看效果。

然后是右边的细节页面。在这个页面中,当窗口宽度变大时,所有控件的宽度也会变大。以TableView相同的方法,分别设置它们的自动布局如下:

·      设置Name标签的左、上约束。

·      设置bugTitleView的左、上约束。

·      设置Rating标签的左、上约束。

·      设置bugRating的左、上、右和高约束。

·      设置bugImageView的左、上、下、右约束。

·      移动 Change Picture 按钮,以便它的右边沿刚好和bugImageView的右边沿平齐,然后设置它的右、下约束。


设置完bugImageView按钮可能会出现几个警告,但ChangePicture 按钮之后,这些警告会消除。

上述步骤做完后,故事板将如下图所示:

编译运行,再次缩放窗口。

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属性前的勾去掉。

在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

letbuttonsEnabled=(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{

funcencodeWithCoder(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{

funcencodeWithCoder(coder:NSCoder){

coder.encodeObject(self.data, forKey:"data")

coder.encodeObject(self.thumbImage, forKey:"thumbImage")

coder.encodeObject(self.fullImage, forKey:"fullImage")

}

}

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

required convenienceinit(coder decoder:NSCoder){

self.init()

self.data=decoder.decodeObjectForKey("data")asScaryBugData

self.thumbImage=decoder.decodeObjectForKey("thumbImage")asNSImage?

self.fullImage=decoder.decodeObjectForKey("fullImage")asNSImage?

}

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

funcsaveBugs(){

letdata=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()

替换为

ifletdata=NSUserDefaults.standardUserDefaults().objectForKey("bugs")as?NSData{

masterViewController.bugs=NSKeyedUnarchiver.unarchiveObjectWithData(data)as[ScaryBugDoc]

}else{

masterViewController.setupSampleBugs()

}

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343