你是一位熟悉另一平台,希望开始 IPhone 开发(从而接触 Objective-C)的软甲开发者?不久之前的我就处于如此境地,而且坦率的说,由于日常工作的需求,我远离开发很久了。
在两年之后,我已经创造了很多 iPhone 和 iPad 的应用。正如我的学习过程千辛万苦,我希望你能够从我所经受的磨难中获得一些益处。
本教程针对有开发经验的读者。假定你分得出 while loop 同 fruit loops 的区别,以及 debug 同 ladybug 的区别!如果你对编程完全陌生,你可以先看看 给高中学生的 iOS 书 系列。
此教程的目标是通过 Objective-C 的基础内容,给予你一些信心。不同于“让我们分析一下语法的每个片段”的做法,你将得到实践和工作实例。这样,当需要更进一步时,你能够更容易的查阅参考(类似 这种)。
在此教程中,你将创建一个从储存的列表中随机抽取格言的简单应用。在此过程中,你将能够熟悉 Objective-C 的某些部分,包括:
- 变量
- 数组
- 属性列表
- 字符串
- 断言
- 随机选择
- 简单的界面对象和事件
我需要先警告你——使用 Objective-C 进行 iPhone 开发的过程很有趣,你可能会有些上瘾。做好放弃一些睡眠,家务堆积的心理准备!:]
在开始前,先确认你拥有一个苹果开发者账号,并配置完毕,同时安装了最新版的 Xcode (你可以在 Mac App Store)免费下载。
如果都准备好了,I will (Objective) C-you after the jump! :]
准备开始
凡事有先后:创建一个 Xcode 项目。这篇教程以 Xcode 4.5+ 和 iOS 6+ 环境为基础——如果你的 Xcode 版本较老,要不然升级,要不然改看本教程 iOS 6 之前的版本。
启动 Xcode 并通过 iOS\Application\Single View Application (单视图应用)模板创建一个新项目。
键入 QuoteGen 作为项目名称,将设备家族设为 iPhone,确认 Use Automatic Reference Counting 和 Use Storyboards 被点选(且其它复选框未点选)。然后就可以点击下一步并选择项目保存路径了。
你可以看到,你的项目中自动创建的文件有: AppDelegate.h,AppDelegate.m,ViewController.h 以及 ViewController.m 。同时还有 MainStoryboard.storyboard。
AppDelegate 包含初始化应用的代码。在此教程中,你只需要知道这些。下面是你需要应对的文件的简要说明:
- MainStoryboard.storyboard 是一个界面布局文件,通过此文件你可以可视化的创建/设计程序在 iPhone 屏幕上的显示界面。
- ViewController.m 是界面控制器类。界面布局文件被链接到此类。这个过程是自动完成的,因此现阶段你你只需要了解你在界面类中设置的对象和事件将会很容易的同你的界面布局链接。同时,这个文件也是承载你即将编写的 Objective-C 代码.
- ViewController.h是界面控制器类的头文件,实体变量以及屏幕界面需要访问的对象和事件都将被定义在这里。
注意:在 Xcode 中创建界面有两种方法——使用 Storyboards 或 Xcode Interface Builder files (XIBs)。每个方式都很好用,在这篇教程里我们将使用 Storyboards,这是现在最流行的方式。不过两种方法非常相似——只要了解了一种,即可触类旁通。
关于 Storyboards 的更多信息,请在之后参阅 此教程。
向前进
首先你要做的事情就是为在此应用中显示的摘引建立几个变量——普通摘引和视频相关摘引。
为此,你需要创建两个 Objective-C 属性。关于属性有一些微妙之处,但现在你只需要将其视为为你的类创建变量的方式。
创建属性很容易——让我们通过为摘引数组创建属性示例。在 ViewController.h 文件中 @interface 和 @end 行之间加入下面一行:
1 | @property (nonatomic, strong) NSArray *myQuotes; |
让我们一点点分解它:
- 首先你需要添加 @property 关键词。
- 然后,你将列出 属性的特性。在此不再深入—— nonatomic 特性将提高线程安全消耗的性能,而 strong 特性则表示只要持有某个指针的对象存在,则此指向具体变量指针将一直保存在内存之中。
- 之后,你将列出 属性的类型。这里选择 NSArray *,意为“指向 NSArray 类的指针”NSArray 是苹果公司提供的一个方便保存列表信息的类——我们很快就会谈到它。
- 最后,添加属性名。
通过添加这一行,你有了一个这个类可以读取和设置的变量!
注意:在过去,在创建属性之后你还需要 @synthesize 处理,而在更早的过去,你还需要手动声明你的实体变量。这一切现在都不必再做了——现在你只需要一行代码,即可添加一个属性。
同样在过去,你需要自己处理所有的内存管理,但通过新的名为 Automatic Reference Counting (ARC) 的特性,这个过程变得自动化了。阅读更多 ARC 相关,参阅 此教程.
我是不是因为知道这一切泄露了我的年纪了? :]
这个应用也将保存一些电影中的名言。因此你需要创建第二个数组:
1 | @property (nonatomic, strong) NSMutableArray *movieQuotes; |
这里使用 NSMutableArray 只是为了说明数组的不同类型。却别在于,在创建 NSArray 之后,你不能增加或减少其项目,而 NSMutabelArray 则随时可以。
体力活
现在,你可以将你最喜欢的名言保存在 myQuotes 数组中了。你将在 viewDidLoad 中完成这一步,此方法在视图(屏幕)第一次被创建时被调用
在 viewDidLoad 的 [super viewDidLoad]; 一行后添加下面的代码。你可以添加你喜欢的名言。这是一项“体力活”,我们可以只加几个数组项。
01 | // 1 - Add array of personal quotes |
04 | @ "Don't cry over spilt milk" , |
05 | @ "Always look on the bright side of life" , |
07 | @ "Can't see the woods for the trees" , |
08 | @ "Better to have loved and lost then not loved at all" , |
09 | @ "The early bird catches the worm" , |
10 | @ "As slow as a wet week" |
这样,我们就将 myQuates 属相填充完毕了。这里有一些你可能没有用过的时髦语法,让我们破解它。
- self 是一个特殊关键词,意为“当前类”——类似其它语言中的 this。
- 你可以通过点+属性名的方式访问类属性——例如:使用 self.myQuotes 访问你之前创建的myQuates 属性。
- 要想创建数组,Objective-C 中有一个方便的新简写——@[ item1, item 2, item 3 ]
- 数组中的每一项都是一个字符串。要在 Objective-C 中创建字符串,你需要为其添加 @ 符号作为前缀。如果你习惯于其它语言,这点很容易被忘记,并导致你的应用崩溃 :] 所以当你的使用字符串的应用崩溃时,重新检查一下你是否忘记使用 @ 符号了!
很好——现在你的名言数组已经准备就绪。是时候添加随机显示名言的代码了。
开始Outlets
你还没创建用户界面,但要做的话,你需要添加一个文本框来显示名言,一个按钮点击获取随机名言。
为了显示一个随机名言在屏幕上,你需要两件事情-一个文本框的引用来设置文本,和按钮被点击的通知。
但是你如何把界面上的事情和代码关联起来?通过两个关键字 – IBOutlet 和 IBAction!
让我们看看它们怎么工作,先看看IBOutlet。在ViewController.h 数组下面添加下面的代码:
1 | @property (nonatomic, strong) IBOutlet UITextView *quoteText; |
这里你像之前一样声明了一个属性(UITextView类型的),但你用一个特殊的关键字标记它 – IBOutlet.
IBOutlet 意味着 quote_text 是一个可以链接到XIB文件的一个界面元素的对象。这样view controller 就可以通过这个属性访问(修改)那个界面元素了。 这样, 我们将设置这个UITextView显示的文本,但你也可以轻松的修改它的颜色,字体,大小,等等。
下来,在属性列表的后面添加下面的代码:
1 | - (IBAction)quoteButtonTapped:(id)sender; |
它定义了一个方法,你必须在这个类里实现它。这是第一次你看到在Objective-C看到定义方法,所以我们一点点的讲解一下:
- 首先你输入一个破折号 -, 表明你要声明一个实例方法。
- 下来你输入这个方法的返回类型。这个方法返回一个 IBAction, 实际上IBAction定义为void - 也就是说这个方法什么也不返回。但 IBAction 有特别的用途 – 它标记这个方法可以链接到一个界面元素的动作上。在这个例子,你把按钮点击动作,关联到这个方法上,也就是当按钮点击后,这个方法被调用。
- 下来你输入方法的名字 – quoteButtonTapped.
- 下面输入冒号,然后在圆括号中输入第一个参数的类型。id 是一个特殊类型,代表“任何从NSObject继承的对象”。通常当你设置按钮或者其他控件的回调时,它们用这个按钮/控件作为第一个参数调用回调。因为你不需要知道它是什么类型,所以你在这里输入id。
- 下来你输入参数的名字 – sender 。
如果你有超过一个的参数,你应该重复步骤3-5。在Objective-C命名方法的语法有点奇怪,但你习惯了就会喜欢上它。
下来,切换到 ViewController.m 文件去添加quoteButtonTapped:的实现。在文件的结尾处(但要在@end之上)添加下面的代码 :
01 | -(IBAction)quoteButtonTapped:(id)sender { |
02 | // 1 - Get number of rows in array |
03 | int array_tot = [self.myQuotes count]; |
04 | // 2 - Get random index |
05 | int index = (arc4random() % array_tot); |
06 | // 3 - Get the quote string for the index |
07 | NSString *my_quote = self.myQuotes[index]; |
08 | // 4 - Display the quote in the text view |
09 | self.quoteText.text = [NSString stringWithFormat:@ "Quote:\n\n%@" , my_quote]; |
让我们一行一行的讲解一下:
- 首先你得到一个数组的数据项个数。这是你第一次看到在Objective-C调用函数的例子。这个语法有点奇怪 – 你输入左方括号([), 然后输入包含你要调用的方法的对象的名字(self.myQuotes), 然后输入你要调用的方法名(count). 最后,你输入右方括号结束方法调用 (]). 注意这个方法不包含任何参数 – 你会在第四步看到包含参数的例子。
- 下来你使用 arc4random 函数来产生一个随机数。arc4random() 是一个常规的 C 风格的函数 (而不是方法), 所以你使用你熟悉和热爱的括号语法。在这个例子,因为你想随机选择一个名言,这个最大可能性就是数组的行数,最小可能性就是0.在Objective-C (就像其他很多的语言), 输入的第一行是从0开始而不是1.
- 下来你要在myQuotes查找一个数据项。 Objective-C’新的字面语法(literal syntax)允许你在访问一个NSArray数据项时使用你这里看到的下标语法。
- 最后,你使用 stringWithFormat 方法格式化最终的输出字符串,这样你就可以在显示名言前显示一个标签和换行。它就象C/C++的printf一样使用变量替换。%f 是浮点型, %d 是整形, %@ 是Objective-C 对象.
现在为了真正的在屏幕上显示名言,你需要把这个类的文本框的outlet链接到你的XIB文件的一个文本框界面元素。
关联控件
让我们看看怎么做, 打开 MainStoryboard.storyboard. 下来,查看Xcode窗口的右边侧边栏。如果你看不到,你需要点击上面工具栏中“Views”这一区中的最右边的按钮,来显示右边的侧边栏。
右边的侧边栏的下半部分有四个tabs,你可以点击相应的图标来切换tab,我们现在要切换到 Object Library.
从 Object Library 拖拽一个文本框(Text View)和一个圆角矩形按钮(Round Rect Button)到我们的view上。按照自己的喜好把它们放好位置。在按钮上加一个标题,例如“名言”。如果喜欢还可以改变一下控件的颜色和字体。你可以通过右边侧边栏的上半部分改变控件大部分属性,它也有好几个可以切换的tab - 我们最常用的是用来定制控件外观的 Attributes Inspector tab.
因为这个文本框只是用来显示,所以要取消 Behavior 中的 Editable 复选框.
现在你需要链接这个按钮和文本框到你之前在类中设置好的outlet和action。
如果没有看到View Controller Scene,点击storyboard 左下方的第一个按钮,是一个三角形按钮,单机此按钮显示或隐藏View Controller Scene。
按着control键,鼠标左键点击左边侧边栏的View Controller,然后拖拽到中间的文本框,然后释放鼠标左键,会弹出一个弹出菜单,选择其中的“quoteText”.
另外的一种方式是,你可以先选中左边栏的view controller,然后点击右边栏上面图标,切换tab到 Connections Inspector tab. 然后就可以看到你的View Controller的所有可用的链接,然后拖拽“quoteText”到中间文本框。
请记住,Storyboard之所以知道你的quoteText属性,是因为你之前添加的IBOutlet关键字!
链接动作
链接一个控件的动作(例如按钮的点击)到一个方法,过程和之前链接控件到属性的第二种过程很类似。
这次,按control键,拖拽View Controller中的按钮, 释放按键,从弹出菜单选择“quoteButtonTapped:” :
同样有另外的方法,先选择按钮,跳转右边栏的tab页到 Connections Inspector,你可以看到按钮的所有事件,拖拽“Touch Up Inside”事件到View Controller,下来的步骤和上面的过程一样。

按下Touch Up Inside”事件,其后的按钮被选中,不要松按钮,向左拖动,出现连接线。到storyboard上的按钮上再松手。
全速前进!
猜怎么了? 你准备好开始摇滚吧。只要在Xcode的“Run”按钮上点击一下(在屏幕顶端的第一个按钮)就开始编译并且在模拟器上运行应用了。
如果期间发生错误,不要紧张。在这个阶段,这些错误的提示都是明确。常常是声明变量是打错字了。记住Xcode是大小写敏感的。
如果的应用通过了编译,并且运行起来了。然后点击"Quote"按钮获取一个随机的名言:
好的 – 你有了个可以工作的应用,而且你已经学了很多关于Objective-C的知识 – 你已经学会创建属性,方法,类,等等!
但是等等 – 还有些不完美的地方! 现在你的名言的列表是硬编码在应用中。如果你可以从外部文件加载他们那该有多好阿?
啊哈,属性列表的精彩之处开始登场了!
属性列表规则
属性列表是一种特殊的 XML 格式,它由苹果公司设计,用来储存基本数据类型如字符串、数字、数组和字典。它创建容易,并以代码的形式读写,因此是在应用中引入少量数据的良好方式。
让我们尝试一下!在左侧栏你的项目 root 上(项目导航栏)右击鼠标创建,并选择 New File 创建新文件。然后选择 iOS\Resource\Property List 模板并单击 Next。选择储存路径(通常是项目文件夹中的某处)并将文件命名为 quotes.plist。
在 Xcode 中,你可以通过网格视图(如同属性的列表)或文本模式编辑属性列表。如果想要以文本方式编辑,在项目导航栏中的 quotes 文件上右击鼠标并选择 Open As\Source Code。
如果你希望通过复制粘贴快速添加所有名言,以源代码打开或许是更快的方法。如果你想这样,你可以尝试使用网格式图方式,并尝试找出如果使用该方法添加与下面的值。现在,通过复制粘贴下面代码到 quotes(用源代码模式) 添加你的电影名言:
001 | <? xml version = "1.0" encoding = "UTF-8" ?> |
002 | <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
009 | < key >category</ key > < string >classic</ string > |
010 | < key >quote</ key > < string >Frankly my dear, I don't give a dam.</ string > |
011 | < key >source</ key > < string >Gone with the wind</ string > |
015 | < key >category</ key > < string >classic</ string > |
016 | < key >quote</ key > < string >Here's looking at you kid.</ string > |
017 | < key >source</ key > < string >Casablanca</ string > |
021 | < key >category</ key > < string >classic</ string > |
022 | < key >quote</ key > < string >There's no place like home.</ string > |
023 | < key >source</ key > < string >Wizard of Oz</ string > |
026 | < key >category</ key > < string >classic</ string > |
027 | < key >quote</ key > < string >Play it again sam.</ string > |
028 | < key >source</ key > < string ></ string > |
031 | < key >category</ key > < string >classic</ string > |
032 | < key >quote</ key > < string >Elementary my dear Watson.</ string > |
033 | < key >source</ key > < string >Sherlock Holmes</ string > |
037 | < key >category</ key > < string >classic</ string > |
038 | < key >quote</ key > < string >Fasten your seatbelts. It's going to be a bumpy night.</ string > |
039 | < key >source</ key > < string >All about Eve</ string > |
043 | < key >category</ key > < string >classic</ string > |
044 | < key >quote</ key > < string >I have not the pleasure of understanding you.</ string > |
045 | < key >source</ key > < string >Pride and Predice</ string > |
049 | < key >category</ key > < string >classic</ string > |
050 | < key >quote</ key > < string >O Romeo, Romeo! wherefore art thou Romeo?</ string > |
051 | < key >source</ key > < string >Romeo and Juliet</ string > |
055 | < key >category</ key > < string >classic</ string > |
056 | < key >quote</ key > < string >To be or not to be</ string > |
057 | < key >source</ key > < string >Hamlet</ string > |
061 | < key >category</ key > < string >classic</ string > |
062 | < key >quote</ key > < string >...Crime is only a left-handed form of human endeavor.</ string > |
063 | < key >source</ key > < string >The Asphalt Jungle</ string > |
067 | < key >category</ key > < string >classic</ string > |
068 | < key >quote</ key > < string >That's, uh, quite a dress you almost have on...What holds it up?</ string > |
069 | < key >source</ key > < string >An American in Paris</ string > |
073 | < key >category</ key > < string >classic</ string > |
074 | < key >quote</ key > < string >Love, desire, ambition, faith - without them life is so simple, believe me</ string > |
075 | < key >source</ key > < string >Invasion of the Body Snatchers</ string > |
079 | < key >category</ key > < string >modern</ string > |
080 | < key >quote</ key > < string >Go ahead make my day.</ string > |
081 | < key >source</ key > < string >Dirty Harry</ string > |
085 | < key >category</ key > < string >modern</ string > |
086 | < key >quote</ key > < string >May the Force be with you.</ string > |
087 | < key >source</ key > < string >Star wars</ string > |
090 | < key >category</ key > < string >modern</ string > |
091 | < key >quote</ key > < string >Hasta la vista, baby</ string > |
092 | < key >source</ key > < string >Terminator</ string > |
095 | < key >category</ key > < string >modern</ string > |
096 | < key >quote</ key > < string >I feel the need for speed.</ string > |
097 | < key >source</ key > < string >Top Gun</ string > |
100 | < key >category</ key > < string >modern</ string > |
101 | < key >quote</ key > < string >She doesn't even go here.</ string > |
102 | < key >source</ key > < string >Mean Girls</ string > |
105 | < key >category</ key > < string >modern</ string > |
106 | < key >quote</ key > < string >It takes a great deal of bravery to stand up to your enemies, but a great deal more to stand up to your friends.</ string > |
107 | < key >source</ key > < string >Harry Potter</ string > |
110 | < key >category</ key > < string >modern</ string > |
111 | < key >quote</ key > < string >I solemnly swear that I am up to no good.</ string > |
112 | < key >source</ key > < string >Harry Potter</ string > |
116 | < key >category</ key > < string >modern</ string > |
117 | < key >quote</ key > < string >You like pain? Try wearing a corset.</ string > |
118 | < key >source</ key > < string >Pirates of the Carribean</ string > |
122 | < key >category</ key > < string >modern</ string > |
123 | < key >quote</ key > < string >Houston, we have a problem.</ string > |
124 | < key >source</ key > < string >Apollo 13</ string > |
128 | < key >category</ key > < string >modern</ string > |
129 | < key >quote</ key > < string >I'll be back.</ string > |
130 | < key >source</ key > < string >The Terminator</ string > |
133 | < key >category</ key > < string >modern</ string > |
134 | < key >quote</ key > < string >E.T. phone home.</ string > |
135 | < key >source</ key > < string >E.T.</ string > |
138 | < key >category</ key > < string >modern</ string > |
139 | < key >quote</ key > < string >Why are you trying so hard to fit in when you were born to stand out?</ string > |
140 | < key >source</ key > < string >What a girl wants</ string > |
144 | < key >category</ key > < string >modern</ string > |
145 | < key >quote</ key > < string >Watch you talkin about Willis?</ string > |
146 | < key >source</ key > < string >Different Strokes</ string > |
150 | < key >category</ key > < string >modern</ string > |
151 | < key >quote</ key > < string >The plane, the plane</ string > |
152 | < key >source</ key > < string >Fantasy Island</ string > |
155 | < key >category</ key > < string >modern</ string > |
156 | < key >quote</ key > < string >D'oh</ string > |
157 | < key >source</ key > < string >The Simpsons</ string > |
160 | < key >category</ key > < string >modern</ string > |
161 | < key >quote</ key > < string >Kids, you tried your best and you failed miserably. The lesson is, never try.</ string > |
162 | < key >source</ key > < string >The Simpsons</ string > |
166 | < key >category</ key > < string >modern</ string > |
167 | < key >quote</ key > < string >You don’t win friends with salad.</ string > |
168 | < key >source</ key > < string >The Simpsons</ string > |
172 | < key >category</ key > < string >modern</ string > |
173 | < key >quote</ key > < string >Whoever said that money doesn't buy happiness didn't know where to shop.</ string > |
174 | < key >source</ key > < string >Gossip Girl</ string > |
178 | < key >category</ key > < string >modern</ string > |
179 | < key >quote</ key > < string >If I were you, I'd accessorize with some gloves. Even a manicure can't mask those peasant hands.</ string > |
180 | < key >source</ key > < string >Gossip Girl</ string > |
184 | < key >category</ key > < string >modern</ string > |
185 | < key >quote</ key > < string >I tried to be diplomatic, but mostly I just lied a lot.</ string > |
186 | < key >source</ key > < string >Twilight</ string > |
191 | < key >category</ key > < string >modern</ string > |
192 | < key >quote</ key > < string >Once people start throwing wet stuff, I go inside.</ string > |
193 | < key >source</ key > < string >Twilight</ string > |
197 | < key >category</ key > < string >modern</ string > |
198 | < key >quote</ key > < string >I don’t like to lie – so there’d better be a good reason why I’m doing it..</ string > |
199 | < key >source</ key > < string >Twilight</ string > |
这里只有少量作为示例的名言。找点乐子,添加你喜欢的那些。如果你比较懒,示例项目中有一个包含很多名言的属性列表
名言被分类为 classic 或 modern 用来说明你以后会了解到的非常好的功能。
你也可以切换到属性列表视图(Property List view) (在Project Navigator上鼠标右键点击quotes文件,选择Open As\Property List) 看看你添加的值在网格视图中如何显示和组织的。现在你知道不同的编辑模式如何工作的了,你可以任意来回切换。
属性列表很酷,但是如果你出错了,就不酷了。新人常犯的错误是忘记结束的tag或者偶然删除掉< 或者 >. 如果你的属性列表不能加载,那你就需要仔细查阅,找出来原因。Xcode早期的版本可以提示错误行号。我想从版本4之后这个有用的特性就去掉了。
如果你被那个错误卡住了,你需要点技巧来查阅你的文件。我用了个方法(坦白说,这个方法用的有点多)来简化查阅:备份我的plist文件,然后依次移除一点内容来定位大致的错误位置。
创建完你可爱的属性列表后,你要准备加载它到数组开始使用。所以,让我们切换回ViewController.m 然后添加下面的代码到viewDidLoad:结尾处:
1 | // 2 - Load movie quotes |
2 | NSString *plistCatPath = [[NSBundle mainBundle] pathForResource:@ "quotes" ofType:@ "plist" ]; |
3 | self.movieQuotes= [NSMutableArray arrayWithContentsOfFile:plistCatPath]; |
就这么简单 – 现在你把你之前输入到属性列表的所有电影名言加载到一个数组里了!
要试用你的新数组,你可能想你只要把电影名言数组替换成你的名言数组就可以了。所以,在quoteButtonTapped: 里你简单的替换所有movieQuotes引用成myQuotes, 对吗?
如果你试了你就会发现,但是只这样做是不行的。因为myQuotes是一个名言字符串数组,但是movieQuotes不是。它是一个字典数组,字典是一列数据,每个数据可以通过唯一的键值(a unique key)来访问。
为什么?因为你设置了属性列表导致的(再仔细看看就会明白了)
注意: 字典(Dictionaries) 是一个 key/value 容器, 类似于其他语言的哈希表(hashtables)。你可以用valueForKey查找字典里每一项。
所以用下列的代码替换quoteButtonTapped,它替换成movieQuotes数组,而且使用正确键值从每项名言字典里获取名言:
01 | -(IBAction)quoteButtonTapped:(id)sender { |
02 | // 1 - Get number of rows in array |
03 | int array_tot = [self.movieQuotes count]; |
04 | // 2 - Get random index |
05 | int index = (arc4random() % array_tot); |
06 | // 3 - Get the quote string for the index |
07 | //NSString *my_quote = [self.myQuotes objectAtIndex:index]; |
08 | NSString *my_quote = self.movieQuotes[index][@ "quote" ]; |
09 | // 4 - Display the quote in the text view |
10 | self.quoteText.text = [NSString stringWithFormat:@ "Quote:\n\n%@" , my_quote]; |
为了后面方便,保持段#3的注释行。编译,运行,享受你新的电影名言吧!
真棒,现在你有了个文件可以从外部文件读取名言了。这非常方便,如果你希望其他人为你填写一些名言。
接下来,你准备玩一些花样了。允许用户选择看 myQuotes 或者 movieQuotes, 和选择看经典或者现代电影名言。
选项,选项,选项
首先你需要回到类的头文件 ViewController.h 并添加一个新的属性。
1 | @property (nonatomic, strong) IBOutlet UISegmentedControl *quoteOpt; |
这里你添加了一个连接到 Segmented Control 的属性,这样你就可以在选择列表中选择一个项目——这是选择名言类型的最佳方式。
现在到
MainStoryboard.storyboard 页面,并将一个 Segmented Control 空间拖到界面中。
将 control 的属性修改如下:
- Style: Bar (my personal preference)
- Segments: 3
- Select Segment: 0 and change the title to: Classic
- Select Segment: 1 and change the title to: Modern
- Select Segment: 2 and change the title to: Mine
这样就获得了三个不同名言类型的效果,或者更准确的说,是三选一的能力。
创建好您的 Segmented Control 后, 您需要将它与您的类建立链接。 你可以使用之前相同的方式将控件链接到视图控制器的 quoteOpt 属性。
这个控件不需要使用行为事件。
为什么您不生成并运行一下看看你屏幕上的新控件呢?目前它还不会有任何功能,但是看到它在那里感觉很不错!
谓词的使用
谓词是一个可以用于过滤数组的对象。它就像我们在 SQL 中 select 查询的 where 子句。 我发现将它用于处理分类属性表非常有用。这样可以节省你不必创建单独的属性列表.
别生我的气,你得回到 ViewController.m 并修改 quoteButtonTapped: 使用 myQuotes 代替 movieQuotes,很快你就将为你的电影对白做一些完全不同的东西。您需要为它设置一个条件,所以你将在 Segmented Control 的第三项选中时使用它。
或者如果你愿意,只需使用以下代码替换 quoteButtonTapped: :
01 | -(IBAction)quoteButtonTapped:(id)sender { |
02 | // 1 - 当第三段选中时获取 quotes |
03 | if (self.quoteOpt.selectedSegmentIndex == 2) { |
05 | int array_tot = [self.myQuotes count]; |
07 | int index = (arc4random() % array_tot); |
08 | // 3 - 从索引处获取 quote 字符串 |
09 | NSString *my_quote = self.myQuotes[index]; |
10 | // 4 - 在文本视图上显示 quote 字符串 |
11 | self.quoteText.text = [NSString stringWithFormat:@ "Quote:\n\n%@" , my_quote]; |
现在当用户选择第三项时,它将只看到 myQuotes 。正如你会发现剩下的代码是和以前一样,唯一的区别是只在段索引2选中时显示 quote(来源于 personal quote 列表的 quote) 您可能还记得,因为段控件在索引0开始,索引2表示的第三个项目。
生成并测试你的代码确保它正常工作,只有选中“Mine”时显示 guotes 。
对谓词的使用,首先你需要通过段控件当前选择的段确定要用于数组过滤的分类,一起来!
这段是 quoteButtonTapped: if 段的后一段内容 – 所以在第一节之后 if 段后如下:
04 | NSString *selectedCategory = @ "classic" ; |
05 | if (self.quoteOpt.selectedSegmentIndex == 1) { |
06 | selectedCategory = @ "modern" ; |
09 | NSPredicate *predicate = [NSPredicate predicateWithFormat:@ "category == %@" , selectedCategory]; |
10 | NSArray *filteredArray = [self.movieQuotes filteredArrayUsingPredicate:predicate]; |
12 | int array_tot = [filteredArray count]; |
13 | // 2.4 - 作为保障只有当数据有内容时继续 |
16 | int index = (arc4random() % array_tot); |
17 | // 2.6 - 获取索引处的 quote 字符串 |
18 | NSString *quote = filteredArray[index][@ "quote" ]; |
19 | self.quoteText.text = [NSString stringWithFormat:@ "Movie Quote:\n\n%@" , quote]; |
21 | self.quoteText.text = [NSString stringWithFormat:@ "No quotes to display." ]; |
好的,生成并运行。检查,你看到的引述取决于你的选择正确的类型。如果你总是得到相同的类型,我的猜测是,你可能没有将 Segmented Control 链接到你的类。
字符串 交响乐
到目前位置一切都很好! 现在让我们探索Objective-C中一些不同的字符串选项和语法吧。
如果这个名言在属性列表中有一个来源(source), 那么我们的应用也应该显示它。检查字符串是否包含值,可以检查字符串的长度。
所以在quoteButtonTapped:的section #2.6第一行之后添加下面的代码(第一行不包括注释行):
1 | // 2.7 - Check if there is a source |
2 | NSString *source = [[filteredArray objectAtIndex:index] valueForKey:@ "source" ]; |
3 | if (![source length] == 0) { |
4 | quote = [NSString stringWithFormat:@ "%@\n\n(%@)" , quote, source]; |
6 | // 2.8 - Set display string |
同样,注释掉这一行,你不在需要它了:
1 | //self.quoteText.text = [NSString stringWithFormat:@"Movie Quote:\n\n%@", quote]; |
你从数组中获得来源(source)然后检查它的长度不为0来确认它是否包含有效值。 ! 代表 NOT. 当检查整数是否相等时使用 == 。
然后你用stringWithFormat 把名言和来源连接起来,构建一个新的字符串来显示。
为了更有趣些,为什么你把来自经典电影的名言显示的稍微不一样些?这需要检查选择的名言归属那个类别。
用下面的代码替换quoteButtonTapped:中的段 #2.8:
1 | // 2.8 - Customize quote based on category |
2 | if ([selectedCategory isEqualToString:@ "classic" ]) { |
3 | quote = [NSString stringWithFormat:@ "From Classic Movie\n\n%@" , quote]; |
5 | quote = [NSString stringWithFormat:@ "Movie Quote:\n\n%@" , quote]; |
8 | self.quoteText.text = quote; |
这里检查是否字符串等于特定的值“classis”,然后特殊显示这个类别里的label。
如果你想检查一个电影的标题(或者其他任意的字符串)以特定的值开始。你也可以做到。如果你想特殊显示一个来自哈利波特电影的的名言 - 添加下面的代码在段 #2.9之上:
1 | if ([source hasPrefix:@ "Harry" ]) { quote = [NSString stringWithFormat:@ "HARRY ROCKS!!\n\n%@" , quote]; } |
就像你猜测的那样, hasPrefix 用来检测是否一个字符串的前缀包含特定的文本值。
编译并运行你的应用,看它是否工作正常。特别注意一下来自不同的类别和来自哈利波特电影的名言是否正确显示。
这是数组名言
为了好玩,你可以把你的所有名言串接起来,就好像他们是一个。这会演示如何数组循环,这在你想遍历处理数组每一个元素是很有用。
在 quoteButtonTapped: 中用下面的代码替换段#1的代码:
01 | if (self.quoteOpt.selectedSegmentIndex == 2) { |
02 | // 1.1 - Get array count |
03 | int array_tot = [self.myQuotes count]; |
04 | // 1.2 - Initialize string for concatenated quotes |
05 | NSString *all_my_quotes = @ "" ; |
06 | NSString *my_quote = nil; |
07 | // 1.3 - Iterate through array |
08 | for ( int x=0; x < array_tot; x++) { |
09 | my_quote = self.myQuotes[x]; |
10 | all_my_quotes = [NSString stringWithFormat:@ "%@\n%@\n" , all_my_quotes,my_quote]; |
12 | self.quoteText.text = [NSString stringWithFormat:@ "%@" , all_my_quotes]; |
一个for循环用于遍历数组第0行(row 0)到最后一行。x是跟踪行数的计数器。
现在运行查看结果。
最后一件事。我之前提过有两种不同的数组:
NSArray 和
NSMutableArray. 到现在为止,这两个类型在这个项目功效一样。
如果你想更新/修改数组,可以使用NSMutableArray。就像它的名字所说,一个可变(mutable)数组可以修改,但一个普通NSArray是不可变的,你不能添加或删除数组项。
例如,在一行选中后为了表明这个名言已经显示过了,你想更新数组,你需要使用 NSMutableArray.
在这个项目, movieQuotes 是你的 NSMutableArray. 你使用一个谓词(predicate), 所以你首先需要找到这一行然后更新它。在quoteButtonTapped:的段#2.9添加下面的代码:
01 | // 2.10 - Update row to indicate that it has been displayed |
02 | int movie_array_tot = [self.movieQuotes count]; |
03 | NSString *quote1 = filteredArray[index][@ "quote" ]; |
04 | for ( int x=0; x < movie_array_tot; x++) { |
05 | NSString *quote2 = self.movieQuotes[x][@ "quote" ]; |
06 | if ([quote1 isEqualToString:quote2]) { |
07 | NSMutableDictionary *itemAtIndex = (NSMutableDictionary *)self.movieQuotes[x]; |
08 | itemAtIndex[@ "source" ] = @ "DONE" ; |
你的循环遍历这个数组,检查每一行看是否是你查找的行。再一次用到 isEqualToString; 但是这次是比较两个字符串变量。
为了更新数组中的这一行,你获取这一行,然后更新对象。这超过了这个教程的知识范围一点点,但你提前了解一点也很有好。
因为你更行了source,而且source是为每个类别来选择名言的。所以下次你用NSPredicate过滤数组是,这一行就不会被包括在结果中了。相当整洁。
下面需要学习什么?
这里是 实例工程文件 包含我们上面教程的所有代码。
好,你已经完成了这个小工程。你已经学会了不同方法使用数组,从界面事件出发动作,在Interface Builder中使用XIB文件访问文件和做各种字符串连接和比较。作为第一个iPhone应用,这个成果还不错!
现在你具备了基础知识,你可以开始试试我们“如何写一个简单iPhone应用“( How To Create a Simple iPhone App)系列教程了。或者订阅我们的月度简报(sign up for our monthly newsletter)获得为初学者写的长篇iOS教程。
如果你有任何问题,可以使用这个论坛。同样,如果你喜欢这个教程而且想看更多这个系列的,请在论坛上告诉我!
同时,祝你好运,生活快乐! :]