原文:http://www.raywenderlich.com/17811/how-to-make-a-simple-mac-app-on-os-x-10-7-tutorial-part-13
原创译文,转载注明出处:http://blog.csdn.net/mamong/article/details/8458224
本教程由iOS Tutorial Team成员Ernesto García发布,他是一位Mac和iOS开发者,CocoaWithChurros.的创始人。
现在是iOS开发者的好时候,你不仅可以在iPhone和iPad应用商店里发布你的应用,而且你还可以使用这些基本技能成为一个Mac开发者,因为它们两者的开发是相当接近的。
假若你是一个iOS开发者,对学习成为一个Mac开发者的基础充满好奇,想了解如何从iOS应用迁移到桌面上来,那么这个教程适合你。
在本教程里,你将建立你的第一个Mac应用,也就是先前我们在教程《How To Create A Simple iPhone App 》中建立的iOS应用的Mac版本。
如果你已经跟着那个教程做过一遍,那么你将对这个里许多步骤相当熟悉,同时,你也会看到iOS和Mac编程之间的差异。
假如你没有看过那个教程,不要着急,这对于阅读和理解本教程不是必要的,我们会指导你一步步走下去。
创建这个应用时,你将会学到如下内容:
本教程适合Mac初级开发者,假定你熟悉objective-c编程和Xcode。为了跟随本教程,建议有iOS编程的知识,但这不是硬性要求。
在本系列分为三部分。在第一部分,我们将涉及到如何下载一张昆虫名录model,并且将它们显示在Table view里。(跳到第二部分或者第三部分)
创建一个Mac项目和创建一个iOS项目是非常相似的,仍然使用Xcode,但是使用不同的模版。前往Xcode菜单“File->New Project",在跳出的窗口里选择“Mac OS X”区域里的“Application”,然后点击“Next”。
在下一页,你将输入这个应用的信息。在“Product Name”里输入“ScaryBugsMac”,选择一个独一无二的“Company Identifier”,苹果建议使用域名的倒序形式。将剩余的文本框留空。
最后,确保只有“Use Automatic Reference Counting”被选中,而其他的选择框没有被选中。当你完成这一切之后,选择“Next”。
现在Xcode会询问你保存项目的路径。选择你电脑里的一个文件夹,然后点击“Create”.
项目已经建立,现在有了一个单个窗口的简单Mac应用。我们来看看它是啥样子的。找到“Run”按钮,它应该在Xcode顶部工具栏中的左边。点击它,xcode就会编译这个应用。当Xcode编译完后,你将会看到应用的主窗口。
这表明了三点:首先,说明你选择了正确的模版,它工作了!其次,说明这是一个不错的起点。第三,说明这和iOS编程有些非常大,而且显而易见的差别:
现在我们来对这个窗口进行点改变,让它显示一些虫子的信息。正如在iOS里一样,第一件要做的事情就是建一个View Controller。在这个view里,你将定义主程序的用户界面。
建立一个View Controller,前往菜单栏“File\New\File…”,在弹出的窗口里,选择“OS X\Cocoa\Objective-C class”,点击“Next”。
将这个类命名为“MasterViewController”,在“Subclass of”中输入“NSViewController”。确保“With XIB for user Interface”被选中,点击“Next”.
在弹出的最后一个窗口再次里选择“Create”。现在一个新的view Controller已经建立。在你的项目导航器应该显示类似如下:
建好view controller后,该是放些UI控件在view上面。在项目导航器里点击“MasterViewController.xib”,它会将你刚才建立的View controller的可视化表现形式呈现给你。
Interface Builder让你以可视化的方式去创建用户界面。你仅仅需要拖曳一个组建到你的View,根据应用的需要,将其放在合适的位置,调整它的大小。
第一个想法,你的应用需要做的事情是显示一个虫子的列表。为此,你需要一个Table View。在OSX里,这个控件叫NSTableView(和iOS里的UITableView类似)。
如果你熟悉iOS编程,你也许能够发现这里的一个模式。许多在UIKit中的用户界面相关的类都是从OSX AppKit中迁移过去的。因此,它们中的一些仅仅是从Mac中的前缀NS变成了iOS中的UI。
因此,根据经验,如果你好奇Mac中是否存在一些你知道的,喜欢的iOS控件,你可以试着找找这些以NS为前缀的类。你会惊讶你找到了这么多---NSScrollView, NSLabel, NSButton,还有更多。注意在某些情况下,这些控件也许和iOS里的变体有些不同。
用户界面控件位于屏幕右边的底部,确保选中第三个标签(UI控件的标签),找到NSTableView(你可以拖动控件列表里的滚动条来找到它,也可以在控件盘的搜索栏里输入NSTableView)。
从控件盘里拖曳一个table view到view上,将它放在左上角附近。不要担心table的尺寸,等下处理这个问题。
你现在有了一个table在上面的view,但是你还没将这个view的控制器添加到主窗口,因此它不会显示出来。你需要在Application Delegate完成这个。在项目导航器里选择“AppDelegate.m”。
为了使用一个新的view controller,必须告诉Application Delegate它的存在,因此你需要做的第一件事情是导入view controller的头文件。添加如下内容到“AppDelegate.m”:在“#import “AppDelegate.h””下一行,”@implementation AppDelegate”之前,添加
#include "MasterViewController.h"
添加如下代码到刚才那行代码的下面,“@implementation AppDelegate”之前。注意根据新的auto-synthesize特性,properties不再需要被synthesize,所以你就这样设置:
@interface AppDelegate()
@property (nonatomic,strong) IBOutlet MasterViewController *masterViewController;
@end
这些要在应用程序启动的时候完成。Application delegate有一个applicationDidFinishLaunching方法,在应用程序启动的时候会被操作系统调用。那就是你添加所有初始化代码的地方,这也意味着这只是在程序启动的时候被执行一次。
假如你熟悉iOS编程,这个方法和iOS里的方法– (BOOL)application:didFinishLaunchingWithOptions:launchOptions是等效的。
让我们来创建view controller,并且将它添加到主窗口。在applicationDidFinishLaunching:中插入如下代码:
// 1. Create the master View Controller
self.masterViewController = [[MasterViewController alloc] initWithNibName:@"MasterViewController" bundle:nil];
// 2. Add the view controller to the Window's content view
[self.window.contentView addSubview:self.masterViewController.view];
self.masterViewController.view.frame = ((NSView*)self.window.contentView).bounds;
最后一行设置你的view的大小以匹配窗口的初始尺寸。再次对比iOS编程,这有些不同。在iOS里,你会设置窗口的根视图控制器(rootViewController),但是根视图控制器在OSX里不存在,因此你需要将你的view添加到窗口的内容视图(content View)里。
假如现在你点击“Run”,你将看到主窗口显示了你那个带有table view的视图。非常好---我们来干点别的。
到目前为止,你已经有了一个包含一个漂亮的table view的窗口。但是事实上它也没做什么。你想让它显示些令人惊慌的虫子的信息----但是等一下,你还没有任何要展示的数据!
没有数据,这真的让我很难过。因此,在接下来的步骤中,你将为应用建立一个数据模型,但是在这之前,教你一个组织项目导航器里文件的方法。
注意:这是一个可选的部分,展示如何组织文件到组里。假如你看过《How To Create A Simple iPhone App on iOS 5 Tutorial》或者已经知道怎么做了,可以跳过这部分。 |
默认模板以应用的名字创建了一个组,还有一个存放支持文件(plist, resources等)。当你的项目变得越来越大,你将和很多文件打交道,查找你要的文件变得越来越困难。
在这部分,我们将教你一个组织你的文件的方法。这个组织方式因人而异,因此你可以根据自己的喜好来改变它。
首先,你需要创建一个组来存放用户界面文件,我们将命名它为“GUI”。创建这样一个组,你可以Ctrl+Click或者右键“ScaryBugsMac”组。在弹出的菜单中选择“New Group”。这个被创建的组自动被选中,你可以输入一个新的名字“GUI”。
现在,拖曳用户界面文件到那个组(AppDelegate.h/.m , MasterViewController.h/.m/.xib andMainMenu.xib)。在拖曳后,你的项目导航器看起来应该类似这样:
现在创建“Scary”的第二个组,命名为“Model”。在接下去里,我们将为你的应用创建一些数据模型文件,你需要将这些文件添加到这个组。到此,你的导航器应该类似这样:
在我们开始之前,让我们谈论下如何组织这些东西:
我们这样设置的理由是为了这个教程的接下去部分更容易些,在那儿我们将开始保存我们的数据到硬盘。
Note: If you’ve followed the How To Create A Simple iPhone App on iOS 5 Tutorial, you will find that this section is (almost) identical to that. One of the good things about Mac/iOS programming is that they share most of the SDK, obviously, except the UI classes and some OS specific parts. So, when you’re creating the model and classes that don’t need user interface, you will find that most of your code will likely just work on Mac, or it will work with some minor changes. For instance, in this case, changing the ScaryBug model classes from iOS to Mac only required one change. UIImage does not exist in OSX, so you just needed to change it to OSX’s image class, NSImage. And that was it! |
在项目导航器里,Control-Click你刚才创建的模型组,在菜单里点击“New File...”,选择“OS X\Cocoa\Objective-C class”模版,然后点击“Next”。
命名这个类为“ScaryBugData”,将它的父类设置为“NSObject”,点击“Next”。
在最后弹出来的窗口里再次点击“Create”。假如一切顺利的话,你的项目导航器应该类似这样子:
接下去我们为“ScaryBugData”添加些源代码。
首先,选中“ScaryBugData.h”文件,用下面的代码替换它里面所有的内容:
#import
@interface ScaryBugData : NSObject
@property (strong) NSString *title;
@property (assign) float rating;
- (id)initWithTitle:(NSString*)title rating:(float)rating;
@end
你还为这个类定义了一个初始化方法,因此你可以在创建一个虫子的时候设置它的名字和发怵等级。转到“ScaryBugData.m”,用下面的代码替换:
#import "ScaryBugData.h"
@implementation ScaryBugData
- (id)initWithTitle:(NSString*)title rating:(float)rating {
if ((self = [super init])) {
self.title = title;
self.rating = rating;
}
return self;
}
@end
好了,“ScaryBugData”就那么多。现在根据前面的步骤创建另外一个NSObject的子类,这次命名为“ScaryBugDoc”。使用下面的代码替换”ScaryBugDoc.h”的内容:
#import
@class ScaryBugData;
@interface ScaryBugDoc : NSObject
@property (strong) ScaryBugData *data;
@property (strong) NSImage *thumbImage;
@property (strong) NSImage *fullImage;
- (id)initWithTitle:(NSString*)title rating:(float)rating thumbImage:(NSImage *)thumbImage fullImage:(NSImage *)fullImage;
@end
#import "ScaryBugDoc.h"
#import "ScaryBugData.h"
@implementation ScaryBugDoc
- (id)initWithTitle:(NSString*)title rating:(float)rating thumbImage:(NSImage *)thumbImage fullImage:(NSImage *)fullImage {
if ((self = [super init])) {
self.data = [[ScaryBugData alloc] initWithTitle:title rating:rating];
self.thumbImage = thumbImage;
self.fullImage = fullImage;
}
return self;
}
@end
现在你编译并且运行你的应用来检查是否一切运行正常。按照预期你应当看到一个空的列表,因为你还没有将数据模型和UI相链接。
现在你有了数据模型,但是你还没有任何数据。你需要创建一个“ ScaryBugDocs”列表,并且你将它存在一个NSMutableArray里。我们将在MasterViewController里添加一个property来跟踪你的虫子列表。
选择“MasterViewController.h”,将下面这行代码放在@interface和 @end lines之间:
@property (strong) NSMutableArray *bugs;
这将是我们用来跟踪虫子列表的实例变量/property。接下去让我们来将它勾起来。
现在,MasterViewController类已经做好准备来接受一个虫子列表。但是话说回来,你还是没有任何数据。
在添加数据之前,我们需要一些令人发怵的从子的图片!你可以从教程《How To Create A Simple iPhone App on iOS 5 Tutorial》中下载这些图片或者去网上找些你喜欢的令人发怵的虫子的图片:]。
下载好这些文件或者得到了你自己喜欢的文件,将它们拖到你的项目导航器的文件结构树的根位置。当弹窗出现时,确保选中了“Copy items into destination group’s folder (if needed)”,然后点击“Add”。
假如你想把这些东西保存得更加合理,你可以为这些虫子图片创建一个子组,然后拖曳这些文件到里面。
现在,让我们来创建样品数据。选中“AppDelegate.m”,将下面一行添加到文件顶部,#include “MasterViewController.h”的下面:
#import "ScaryBugDoc.h"
// Setup sample data
ScaryBugDoc *bug1 = [[ScaryBugDoc alloc] initWithTitle:@"Potato Bug" rating:4 thumbImage:[NSImage imageNamed:@"potatoBugThumb.jpg"] fullImage:[NSImage imageNamed:@"potatoBug.jpg"]];
ScaryBugDoc *bug2 = [[ScaryBugDoc alloc] initWithTitle:@"House Centipede" rating:3 thumbImage:[NSImage imageNamed:@"centipedeThumb.jpg"] fullImage:[NSImage imageNamed:@"centipede.jpg"]];
ScaryBugDoc *bug3 = [[ScaryBugDoc alloc] initWithTitle:@"Wolf Spider" rating:5 thumbImage:[NSImage imageNamed:@"wolfSpiderThumb.jpg"] fullImage:[NSImage imageNamed:@"wolfSpider.jpg"]];
ScaryBugDoc *bug4 = [[ScaryBugDoc alloc] initWithTitle:@"Lady Bug" rating:1 thumbImage:[NSImage imageNamed:@"ladybugThumb.jpg"] fullImage:[NSImage imageNamed:@"ladybug.jpg"]];
NSMutableArray *bugs = [NSMutableArray arrayWithObjects:bug1, bug2, bug3, bug4, nil];
self.masterViewController.bugs = bugs;
你终于有了些数据!编译运行你的程序,确保所有都正常工作,没有发生任何错误。但是我们仍然在用户界面看不到任何东西,但是这次view controller已经有它需要的数据了,我们可以着手用户界面上的工作,最终显示出你的发怵小虫子列表。
为了显示虫子列表,你需要设置table view从你的模型中得到虫子列表。
在OSX中,table view控件叫做NSTableView,它和UITableView类似之处是它也能显示成列的数据,但是不同之处是NSTableView每一行可以显示多列。
和UITableView一样,NSTableView每一行都有些cell。然而它们的功能最近有了些改变:
在本教程里,你将使用这样新的基于view的Table view。我们将涉及到基础部分,但是假如你想学习更多关于NSTableView的,你可以阅读《Table View Programming Guide》,在这里面非常好的解释了table view如何工作的。
在设置用户界面之前,你需要在nib文件里做一个小小的改变,关闭“Auto Layout”。Auto Layout是OSX10.7中新介绍的特性,意在根据程序员订下的一些规则,自动处理用户界面上的控件尺寸改变。Auto Layout超出了本教程的范围,使解释一些东西变得比较困难,因此你要先关掉它。在Auto Layout关掉之后,autoresizing就可以配置,并且和在iOS 5项目里具有相同的运行结果。
选择MasterViewController.xib。Interface Builder界面打开,在窗口右侧的实用工具盘(Utilities panel)里,确保选中“File Inspector”(它位于左侧标签栏第一个)。在“File Inspector”标签中取消选中“Use Auto Layout”。
做完这些,你需要对main window做同样的操作。选中“MainMenu.xib”里的window,同样取消auto layout。
现在我们已经准备好了。让我们开始设置你的table view,这样它就可以显示一个ScaryBugDocs列表了。在项目导航器里选择“MasterViewController.xib”,在Interface Builder的view里选中你的table view。注意table view是内嵌在一个scroll view里的。因此你第一次点击它,你会选中scroll view。
为了选中table view,再次点击它(不是双击,第二次点击在前一次后面一小会儿)。另外一个选中它的办法是直接点击右侧对象盘(objects panel)里的table view。
选中之后紧接着要做的事情是改变table view为“view based”,因为Interface builder默认创建的是“cells based”。
为了改变它,确保你选中了屏幕右侧的特性盘(properties panel)的“Attributes Inspector Tab”。然后在“Content Mode” 中选择 “View Based”。你的列表不需要很多列,因此改变“Columns property”为1。
为了定制这个列表,选中特性“Alternating Rows”,这样就会以蓝白交替的方式绘制行。不要选中“Headers”特性,这样就移除了表头,因为在本教程里我们不需要。
在移除多余的列之后,剩下的列比table view窄,为了调整它的大小,点击“table column”(点击三次table view或者使用右侧的对象盘),调整这一列的大小以铺满整个table的宽度。
接下来的步骤是配置table view使用的cell view。你的列表需要显示虫子的图片和名字。你需要包含一个image和一个text field的cell来显示信息。Interface Builder有一个预配置的包含一个image和text field的NSTablecellView,因此你要用那个。在窗口底部左侧的“Object library panel”里有一个“Image & Text Table Cell View”,将它拖到你的table view里。
做完这些之后,你的table里现在有两种不同的cell。选中原来的cell(没有齿轮图标的cell),按下delete键来删除。
最后一步是改变cell的高度,因为它对于显示虫子的图片来说是在太小了。你应当将其的高度设置为32.选中cell,然后在Xcode窗口的右侧特性盘(Utilities panel)里打开大小检查器“Size Inspector”标签。你可以在高度配置盘(Height panel)里设置cell的高度为32.
另外一种办法是拖动cell底部的边框直到你得到了想要的高度。这之后,image和text field有了些偏移,为了修复这个,选中它们,然后拖到cell的中间。你可以改变image view的大小,改变text field的字体来满足你的需要。现在设计的table view应该像这样:
现在你要设置列标识(column identifier),这是一个你给table view的每一列取的名字,因此当你想要执行一些操作或者你收到了一些来自某列的通知,你就可以识别出是哪一列。
这并非本教程严格需要的,因为你只有一列,但是这样做是一个不错的练习,当在其他项目里需要建立一个多列的table view的时候,你就不会有什么问题了。选中table column(记住你要在table view中点击三次或者使用左边的Objects panel)。在这之后,打开Utilities panel里的“Identity Inspector”标签。在那里Identifier由“Automatic”变为 “BugColumn”。
这就是table UI的配置。现在你需要连接table view和MasterViewController,这样它们就能知道彼此的存在了。
和iOS一样,table view使用两个property来让table和它的controller来通信:数据源(datasource)和委托(delegate)。
基本上,数据源是一种告诉table view它要显示什么数据的类。委托也是一个类,它控制数据如何显示,并且从table view接收通知,例如当一个cell被选中。
委托和数据源通常是(但是现在是总是)同一个controller。这本例子中,数据源和委托都是你的MasterViewController类。这可以通过编程来实现,但是在本教程中,你将要在Interface Builder来实现这种关联。
选中你的table view,在“Utilities Panel”里选择连接检查器(Connections Inspector,有个指向右侧箭头图标的那个)。在Outlets区域,你会看到delegate和datasource。(原文此处有错误“you will see the delegate in the datasource”,翻译版本已修复)先来连接delegate。点击delegate右侧的小圆圈,然后拖到左侧PlaceHolders panel的File’s Owner上(也就是MasterViewController)。
正如你被告知的那样,table view的delegate是MasterViewController。当你实例化你应用里的视图控制器,将为我们自动创建这种连接。现在对datasource outlet重复这个过程。做完这些后,你一定可以看到两条指向File’s Owner的连接,像这样:
就这样,现在我们要往你的view controller里添些必要的代码来显示你的虫子列表。选择MasterViewController.m,将下面的添加到文件的顶部,#import “MasterViewController.h”这一行下边:
#import "ScaryBugDoc.h"
#import "ScaryBugData.h"
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
// Get a new ViewCell
NSTableCellView *cellView = [tableView makeViewWithIdentifier:tableColumn.identifier owner:self];
// Since this is a single-column table view, this would not be necessary.
// But it's a good practice to do it in order by remember it when a table is multicolumn.
if( [tableColumn.identifier isEqualToString:@"BugColumn"] )
{
ScaryBugDoc *bugDoc = [self.bugs objectAtIndex:row];
cellView.imageView.image = bugDoc.thumbImage;
cellView.textField.stringValue = bugDoc.data.title;
return cellView;
}
return cellView;
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
return [self.bugs count];
}
使用这个方法,table view就知道了要显示多少行,但是仍然不知道显示在每列里的哪个cell,也不知道那些cell应该有什么信息。
这些由tableView:viewForTableColumn:row方法来完成。这个方法由操作系统为table view里的每行和每列调用。在方法里,你需要创建合适的cell,并且用你需要的信息填充它。假如你有iOS编程经验,你会发现这和UITableView的工作方式非常类似。numberOfRowsInTableView:非常类似iOS中的numberOfRowsInSection:,同样地,viewForTableColumn:row也和iOS中的cellForRowAtIndexPath:类似。不同之处在于在iOS里你需要根据它的section 和row来设置cell,而在OSX里,你根据row和column来设置cell。cellForRowAtIndexPath:是一个非常重要的方法,因此让我们来仔细地看一下这个方法:
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
// Get a new ViewCell
NSTableCellView *cellView = [tableView makeViewWithIdentifier:tableColumn.identifier owner:self];
// Since this is a single-column table view, this would not be necessary.
// But it's a good practice to do it in order by remember it when a table is multicolumn.
if( [tableColumn.identifier isEqualToString:@"BugColumn"] )
{
ScaryBugDoc *bugDoc = [self.bugs objectAtIndex:row];
cellView.imageView.image = bugDoc.thumbImage;
cellView.textField.stringValue = bugDoc.data.title;
return cellView;
}
return cellView;
}
这就是所有你需要显示在table view里的信息。这仅仅是一个在Interface Builder中定义properties和connections,在你的view controller里实现两个方法的问题。
是时候编译运行这个程序了。假如一切正常的话,你应该可以看到一个显示Scary Bugs列表的table view!
Here is a sample project with all of the code we’ve developed so far in this tutorial series.
Next in the Series, you will learn how to add a detail view, and how to add/edit/delete bugs from your list. you will also cover how to polish the User Interface and handle the window resizing to make your app look great at any size!
This is a post by iOS Tutorial Team Member Ernesto García, a Mac and iOS developer founder of CocoaWithChurros.