原文: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
}
}
注意thumbImage和fullImage声明为可空的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采用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 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()方法——只要用户进行过新建、删除和编辑操作。