【译】Experienced programmers but new to Objective-C(一)
注:这是raywenderlich博客上的一个系列文章。是写给从其他语言转到OC语言上的程序员的,一共5节。最近打算学习一下,并且把一些重要的知识点摘抄并且尝试翻译一下,第一次翻译,有些原文如果不知道如何翻译,放上原文。(这TM还算翻译)
一、Objectively Speaking: A Crash Course in Objective-C for iOS 6
In this tutorial, you will create a simple app that randomly generate quotes from a stored list. In doing so, you’ll become acquainted with a number of aspects of Objective-C, including:
- Variables
- Arrays
- Property lists
- Strings
- Predicates
- Random selection
- Simple interface objects and events
Let me warn you though – iPhone development with Objective-C is a lot of fun and a little addictive. Be prepared to give up some sleep, and the chores might build up! :]
Before you begin, make sure you have an Apple developer account with the provisioning details set up and the latest version of Xcode installed (you can download this for free on the Mac App Store).
When you’re done, I will (Objective) C-you after the jump! :]
在这门课程中,你将会创建一个简单的App that randomly generate quotes from a stored list。做的过程中,你将会对OC的很多方面有所了解,比如:
- 变量
- 数组
- plist(特性表)
- 字符串
- Predicates(判断?)
- Random selection(随机选择?)
- 简答的接口对象和事件
首先我得提醒你,苹果开发很欢乐并且容易上瘾。做好放弃一些睡眠和家务的准备。
在你开始之前,确认你有一个苹果开发者的账号以及最新版本的XCode。
当你做完这些,我将会叫你如何起跳。( I will (Objective) C-you after the jump!)
Getting Started
- ViewController.m is the interface controller class. The interface layout file is linked to this class. This happens automatically, so at this stage you don’t need to think about it other than to know that any objects or events you set up in your interface class can be linked to your interface layout very easily. This is the file that will contain the Objective-C code you’re about to create.
- ViewController.h is the interface controller class header file, where you’ll declare the instance variables, as well as the objects and events that you need to access from the interface screen.
Heading in the Right Direction
To do this, you will create two Objective-C properties. There’s some subtle aspects to properties, but for now just think of them as a way to create variables on your class.
It’s very easy to create a property – let’s try it out by adding a property for an array of quotes. Add this line to ViewController.h, between the @interface and @end lines:
@property (nonatomic, strong) NSArray *myQuotes; |
Let’s break this down bit-by-bit:
- First you need to add the @property keyword.
- Then you list the property attributes. Without going too deeply into this topic – the nonatomic attribute increases performance at the cost of thread-safety, and the strong attribute indicates that a pointer to the specified variable will stay in memory as long as the object that holds the pointer exists.
- Then you list the type of the property. Here you choose NSArray *, which means “pointer to an NSArray class.” NSArray is a handy class provided by Apple that stores lists of data – we’ll talk more about this soon.
- Finally, you add the property name.
By adding this single line, you now have a variable you can set and get on this class!
Note: In the old days, you also had to @synthesize your properties after creating them, and in the really old days you had to manually declare your instance variables as well. This is no longer the case – now all you need to do to add a property is add this single line!
Also in the old days, you used to have to do all the memory management yourself, but now with a new feature called Automatic Reference Counting (ARC) it’s automatic. To read more about ARC, check out this tutorial.
Am I showing my age by knowing all this? :]
This app is also going to store some famous quotes from movies. For this, you need a second array:
@property (nonatomic, strong) NSMutableArray *movieQuotes; |
Here you’re using an NSMutableArray simply to illustrate the different types of arrays. The difference is you cannot add or remove items to an NSArray after you create it, but you can add or remove items from NSMutableArray whenever you want.
这一节关于建立property属性。和成员变量比起来有些微妙的差别,但是一开始我们可以把它当做是成员变量。
- 你首先加一个@property关键字
- 然后列出property attributes(属性),稍微提一下,nonatomic保证了线程安全,而strong表明直到对象消失,指针才会消失
你类中现在有了一个可以set和get的变量。通过这一行代码。
Manual Labor
Now you can store your favorite quotes in the myQuotes array. You’ll do this inviewDidLoad, which is the method that executes when the view (screen) is first created.
In viewDidLoad, add the following code after [super viewDidLoad];. Include your own favorite quotes if you like. This is the “manual labor” approach (途径)and is quite okay for a small number of array entries.(词目)
// 1 - Add array of personal quotes self.myQuotes = @[ @"Live and let live", @"Don't cry over spilt milk", @"Always look on the bright side of life", @"Nobody's perfect", @"Can't see the woods for the trees", @"Better to have loved and lost then not loved at all", @"The early bird catches the worm", @"As slow as a wet week" ]; |
Here you are setting the myQuotes property to a list of quotes. There’s some funky syntax(语法) you might not be used to here, so let’s break it down.(休息一下?)其实是让我们把这个问题分析一下。
- self is a special keyword that means “this current class” – kind of like this in other programming languages.
- You can access properties on the class by using a dot, and then typing the name of the property – i.e.self.myQuotes lets you access the myQuotes property you created earlier.
- To create an array, there’s a handy(便利的) new shortcut(捷径) in Objective-C – @[ item1, item 2, item 3 ].
- Each item in the array is a string. To create strings in Objective-C, you need to prefix them with the @symbol. If you’re used to other languages, this can be easy to forget, which will probably cause your app to crash :] So if your app crashes when it uses a string, double check you remembered to use the @ symbol!
Great – now you have an array of quotes ready to go. It’s time to add some code that will allow you to display a random quote on the screen!
Going to the Outlets
You haven’t created the user interface yet, but when you do you’ll be adding a text view to show the quote, and a button to tap to get a random quote.
To display a random quote to the screen, you’ll need two things – a reference to the text view so you can set the text appropriately(适当地,合适的), and notification when the button is tapped.
But how do you connect what goes on in the interface with your code? Through some special keywords – IBOutlet and IBAction!
Let’s see how this works, starting with an IBOutlet. Add the following to ViewController.h under the arrays:
@property (nonatomic, strong) IBOutlet UITextView *quoteText; |
Here you declare a property just like before (for a UITextView class), but you mark it with a special keyword – IBOutlet.
IBOutlet means that quote_text is an object that can be linked to an interface element on the XIB file so that the view controller can access (or change) properties of the interface element. In this case, we’ll be setting the displayed text for the UITextView control but you could just as easily change the color, font, size, etc.
Next, add the following line of code after the list of properties:
- (IBAction)quoteButtonTapped:(id)sender; |
This defines a method that you will implement in this class. This is the first time you’ve seen a method defined in Objective-C, so let’s go over it bit-by-bit:
- First you put a dash -, which indicates you are defining an instance method.(首先你书写了一个破折号,表明你正在定义一个实例变量)
- Then you put the return value of the method. This particular method returns an IBAction, which is actually defined to void – i.e. the method returns nothing. But IBAction has another special property – it marks the method as something to you can connect to an action on a UI element. In this case, you’ll be hooking things up so when the button gets tapped, this method gets called.
- Next you put the name of the method – quoteButtonTapped in this case.
- Then you put a colon(冒号), and in parenthesis(圆括号) put the type of the fist parameter(参数). id is a special type that means “any object that derives from(源于) NSObject”. Usually when you set up callbacks that buttons and other controls will call, they pass whatever button/control is sending the callback as the first parameter. Since you don’t necessarily know what type it is, you put id here.
- Then you put the name of the parameter – sender in this case.
If you had more than one parameter, you would just repeat steps 3-5 multiple times. The syntax of naming methods is a little strange in Objective-C, but you’ll like it when you get used to it.
Next, switch to ViewController.m to add the implementation of quoteButtonTapped:. Add this to the end of the file (but above @end):
-(IBAction)quoteButtonTapped:(id)sender { // 1 - Get number of rows in array int array_tot = [self.myQuotes count]; // 2 - Get random index int index = (arc4random() % array_tot); // 3 - Get the quote string for the index NSString *my_quote = self.myQuotes[index]; // 4 - Display the quote in the text view self.quoteText.text = [NSString stringWithFormat:@"Quote:\n\n%@", my_quote]; } |
Let’s go over this line by line:
- First you get the count of items in an array. This is the first example you’ve seen of calling a method in Objective-C. The syntax is a little strange – you put a bracket ([), then the name of the object you're calling the method on (self.myQuotes), then the name of the method you're calling (count). Finally, you end the method call with a close bracket (]). Note this method doesn’t take any parameters – you’ll see an example that does in step 4.
- Next you use the arc4random function to generate a random number. arc4random() is a regular C-style function (not a method), so you use the regular parenthesis syntax you know and love. In this case, since you want to randomly select one of the quotes, the highest possible value is the number of rows in the array, and the lowest possible value is 0. In Objective-C (like many other languages), the first row in an array is row 0, not 1.
- Next you look up an item in myQuotes. Objective-C’s new literal syntax allows you to access elements in an NSArray by simple subscripting like you can see here.
- Finally, you use stringWithFormat method to format the final output string so that you can display a label and add a new line before displaying the quote. It uses variable substitution, like printf in C/C++. %f is float, %d is integer, and %@ is Objective-C object.
Now in order to actually see the quote on the screen, you need to link the text field outlet in the class with a text field control in your XIB file.
Hooking Up a Control
To see it all in action, open up MainStoryboard.storyboard. Next, look for the right sidebar in your Xcode window. If you don’t see one, you might need to use the rightmost button under the Views section, on the toolbar at the top, to make the right hand sidebar visible.
The lower part of the righthand sidebar has four tabs, which you can switch between using the icons at the top of that section. The section you want is the Object Library.
From the Object Library, drag a Text View control and a Round Rect Button onto the view. Position them to your liking. Add a title to the button, such as “Quote”. Change the color and font for the controls as you see fit. You can change most properties for the controls via the upper part of the right hand sidebar which too has several tabs that you can switch between – the one you’ll use the most to customise the appearance of the controls is the Attributes Inspector tab.
As the text field is for display only, untick Behavior – Editable.
Now you need to link the button and the text view to the outlet and action you already set up in your class.
Link your text view by control-clicking the View Controller in the left sidebar, and dragging to the text view. Release, and select quoteText from the popup that appears.
Alternatively, you can simply select the view controller in the left sidebar and then switch the top portion of the right hand sidebar to the Connections Inspector tab. You should see all the connections available for your View Controller. You can now simply drag from the available connection to the control visible on screen.
Remember the reason why the Storyboard knows about your quoteText property is because you added the IBOutlet keyword earlier!
Hooking Up an Action(连接一个动作)
Hooking up an action on a control (such as a button tap) to a method is quite similar to the process of hooking up a control to a property.
This time, control-drag from the button up to the View Controller, release, and choose quoteButtonTapped: from the popup:
Alternatively, you can simply select the button, and if you have the Connections Inspector selected in the right hand sidebar, you’ll notice that you get a list of events for the button. You can drag from Touch Up Inside event to the View Controller there as well.
Let Her Rip!
Guess what? You’re ready to rock ‘n’ roll. Simply click on the Xcode Run button (the first button at the top of the screen) to compile and run your app in the simulator.
If there are errors, don’t panic. At this stage, they should be self-explanatory. Often just typos when declaring your variables. Remember, Xcode is case-sensitive(有大小写之分).
If your app does compile and run, then click on the Quote button to get a random quote:
Allright – you have a functional app, and you’ve learned a ton about Objective-C already – you’ve created properties, your own method, made use of classes, and tons more!
But wait – there’s more! Right now your list of quotes are hard-coded into the app. Wouldn’t it be a lot better if you could load them from an external file?
Ah, the joys of property lists are about to be revealed!(显示)
Property Lists Rule!
A property list is a special XML format defined by Apple that are designed to store basic data types like strings, numbers, arrays, and dictionaries. They are very easy to create, and to read and write from code, so they are a great way to get in small bits of data into your app.
Let’s try this out! Create a new file by right-clicking on your project root on the left sidebar (the Project Navigator) and selecting New File. Then select theiOS\Resource\Property List template and click Next. Select the location to save your new file (usually somewhere within the project folder for the tutorial project) and name the file quotes.plist.
You can either edit the property list file from within Xcode in a grid-view (as a list of properties) or as a text file. If you want to edit as a text file, right-click on the quotes file on the Project Navigator and select Open As\Source Code.
Since you want to quickly add all the quotes by copying and pasting, opening as source code probably would be the faster route. If you want though, you can try the grid view approach and try to figure out how to add the same values as below using that method.
Now, add your movie quotes by copying and pasting the following into quotes (in source code mode):
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!-- Quotes --> <plist version="1.0"> <array> <dict> <key>category</key> <string>classic</string> <key>quote</key> <string>Frankly my dear, I don't give a dam.</string> <key>source</key> <string>Gone with the wind</string> </dict> <dict> <key>category</key> <string>classic</string> <key>quote</key> <string>Here's looking at you kid.</string> <key>source</key> <string>Casablanca</string> </dict> <dict> <key>category</key> <string>classic</string> <key>quote</key> <string>There's no place like home.</string> <key>source</key> <string>Wizard of Oz</string> </dict> <dict> <key>category</key> <string>classic</string> <key>quote</key> <string>Play it again sam.</string> <key>source</key> <string></string> </dict> <dict> <key>category</key> <string>classic</string> <key>quote</key> <string>Elementary my dear Watson.</string> <key>source</key> <string>Sherlock Holmes</string> </dict> <dict> <key>category</key> <string>classic</string> <key>quote</key> <string>Fasten your seatbelts. It's going to be a bumpy night.</string> <key>source</key> <string>All about Eve</string> </dict> <dict> <key>category</key> <string>classic</string> <key>quote</key> <string>I have not the pleasure of understanding you.</string> <key>source</key> <string>Pride and Predice</string> </dict> <dict> <key>category</key> <string>classic</string> <key>quote</key> <string>O Romeo, Romeo! wherefore art thou Romeo?</string> <key>source</key> <string>Romeo and Juliet</string> </dict> <dict> <key>category</key> <string>classic</string> <key>quote</key> <string>To be or not to be</string> <key>source</key> <string>Hamlet</string> </dict> <dict> <key>category</key> <string>classic</string> <key>quote</key> <string>...Crime is only a left-handed form of human endeavor.</string> <key>source</key> <string>The Asphalt Jungle</string> </dict> <dict> <key>category</key> <string>classic</string> <key>quote</key> <string>That's, uh, quite a dress you almost have on...What holds it up?</string> <key>source</key> <string>An American in Paris</string> </dict> <dict> <key>category</key> <string>classic</string> <key>quote</key> <string>Love, desire, ambition, faith - without them life is so simple, believe me</string> <key>source</key> <string>Invasion of the Body Snatchers</string> </dict> <dict> <key>category</key> <string>modern</string> <key>quote</key> <string>Go ahead make my day.</string> <key>source</key> <string>Dirty Harry</string> </dict> <dict> <key>category</key> <string>modern</string> <key>quote</key> <string>May the Force be with you.</string> <key>source</key> <string>Star wars</string> </dict> <dict> <key>category</key> <string>modern</string> <key>quote</key> <string>Hasta la vista, baby</string> <key>source</key> <string>Terminator</string> </dict> <dict> <key>category</key> <string>modern</string> <key>quote</key> <string>I feel the need for speed.</string> <key>source</key> <string>Top Gun</string> </dict> <dict> <key>category</key> <string>modern</string> <key>quote</key> <string>She doesn't even go here.</string> <key>source</key> <string>Mean Girls</string> </dict> <dict> <key>category</key> <string>modern</string> <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> <key>source</key> <string>Harry Potter</string> </dict> <dict> <key>category</key> <string>modern</string> <key>quote</key> <string>I solemnly swear that I am up to no good.</string> <key>source</key> <string>Harry Potter</string> </dict> <dict> <key>category</key> <string>modern</string> <key>quote</key> <string>You like pain? Try wearing a corset.</string> <key>source</key> <string>Pirates of the Carribean</string> </dict> <dict> <key>category</key> <string>modern</string> <key>quote</key> <string>Houston, we have a problem.</string> <key>source</key> <string>Apollo 13</string> </dict> <dict> <key>category</key> <string>modern</string> <key>quote</key> <string>I'll be back.</string> <key>source</key> <string>The Terminator</string> </dict> <dict> <key>category</key> <string>modern</string> <key>quote</key> <string>E.T. phone home.</string> <key>source</key> <string>E.T.</string> </dict> <dict> <key>category</key> <string>modern</string> <key>quote</key> <string>Why are you trying so hard to fit in when you were born to stand out?</string> <key>source</key> <string>What a girl wants</string> </dict> <dict> <key>category</key> <string>modern</string> <key>quote</key> <string>Watch you talkin about Willis?</string> <key>source</key> <string>Different Strokes</string> </dict> <dict> <key>category</key> <string>modern</string> <key>quote</key> <string>The plane, the plane</string> <key>source</key> <string>Fantasy Island</string> </dict> <dict> <key>category</key> <string>modern</string> <key>quote</key> <string>D'oh</string> <key>source</key> <string>The Simpsons</string> </dict> <dict> <key>category</key> <string>modern</string> <key>quote</key> <string>Kids, you tried your best and you failed miserably. The lesson is, never try.</string> <key>source</key> <string>The Simpsons</string> </dict> <dict> <key>category</key> <string>modern</string> <key>quote</key> <string>You don’t win friends with salad.</string> <key>source</key> <string>The Simpsons</string> </dict> <dict> <key>category</key> <string>modern</string> <key>quote</key> <string>Whoever said that money doesn't buy happiness didn't know where to shop.</string> <key>source</key> <string>Gossip Girl</string> </dict> <dict> <key>category</key> <string>modern</string> <key>quote</key> <string>If I were you, I'd accessorize with some gloves. Even a manicure can't mask those peasant hands.</string> <key>source</key> <string>Gossip Girl</string> </dict> <dict> <key>category</key> <string>modern</string> <key>quote</key> <string>I tried to be diplomatic, but mostly I just lied a lot.</string> <key>source</key> <string>Twilight</string> </dict> <dict> <key>category</key> <string>modern</string> <key>quote</key> <string>Once people start throwing wet stuff, I go inside.</string> <key>source</key> <string>Twilight</string> </dict> <dict> <key>category</key> <string>modern</string> <key>quote</key> <string>I don’t like to lie – so there’d better be a good reason why I’m doing it..</string> <key>source</key> <string>Twilight</string> </dict> </array> </plist> |
These are just a few quotes to serve as examples. Have some fun and add your own favorites. If you’re feeling lazy, the sample project has a property list of many quotes.
The quotes are categorized(分类) as either classic or modern to illustrate(举例) a really neat feature that we’ll get to a bit later.
You can also switch over to the Property List view (right-click on the quotes file on the Project Navigator and select Open As\Property List) to see how the values you added look in the grid view and how it is organised. Now that you know how the different editing modes work, you can always switch back and forth as you like.
Property lists are cool, but can be very uncool when you get an error. Common mistakes for newbies are forgetting the end tag or accidentally deleting a < or >. If your property list doesn’t load, then you’ll need to trawl through and work out why. Earlier versions of Xcode gave line numbers for errors. I think it was from version 4 onwards that this helpful feature was excluded.
If you really get stuck, you need to methodically go through your file. I do this (a bit too often to be frank) to make it easier: copy my plist file, then remove chunks a bit at a time to identify the approximate location of the error.(移除掉大块内容,确定错误大概的位置)
Having created your lovely property list, you are now ready to load it into an array for use. So let’s switch back to ViewController.m and add the following to the end of viewDidLoad:
// 2 - Load movie quotes NSString *plistCatPath = [[NSBundle mainBundle] pathForResource:@"quotes" ofType:@"plist"]; self.movieQuotes= [NSMutableArray arrayWithContentsOfFile:plistCatPath]; |
It’s as easy as that – now you have an array with all of the movie quote data you entered in the property list!
To try out your new array, you might think that all you really need to do is change getting the random quote from your personal quotes array to the movie quotes array. So, in quoteButtonTapped: you simply replace all references to myQuotes with movieQuotes, right?
But that alone will not work, as you will find if you try it. This is because myQuotes was an array of quote strings. But movieQuotes is not an array of strings. Instead, it’s an array of dictionaries where a dictionary is a list of values where you can access each value based on a unique key.
Why? Because that’s how you set up the property list (go look at it again to see!)
Note: Dictionaries are key/value stores, similar to hashtables in other languages.(有点先其他语言中的哈希表) You can look up entries in a dictionary with the valueForKey method.
So replace quoteButtonTapped with the following code which switches over to using the movieQuotes array but also gets the quote by using the right key for each quote dictionary:
-(IBAction)quoteButtonTapped:(id)sender { // 1 - Get number of rows in array int array_tot = [self.movieQuotes count]; // 2 - Get random index int index = (arc4random() % array_tot); // 3 - Get the quote string for the index //NSString *my_quote = [self.myQuotes objectAtIndex:index]; NSString *my_quote = self.movieQuotes[index][@"quote"]; // 4 - Display the quote in the text view self.quoteText.text = [NSString stringWithFormat:@"Quote:\n\n%@", my_quote]; } |
Keep the commented out line in section #3 as it will come in handy later. Build and run and enjoy your new movie quotes!
Awesome, now you have a file that can read quotes from an external file! This can be especially handy if you want someone else to fill in some quotes for you as you continue to work on your app.
Next you’re going to get a bit fancy and allow the user to select between viewing myQuotes or movieQuotes, and whether to view classic or modern movie quotes.
Options, Options, Options
First you need to go back to your class header file, ViewController.h, and add a new property.
@property (nonatomic, strong) IBOutlet UISegmentedControl *quoteOpt; |
Here you’ve added a property that you’ll hook up to a segmented control which will allow you to select one item from a list of options – perfect for selecting a quote type!
Now go to MainStoryboard.storyboard and drag a Segmented Control onto your view.
Change the properties of the control to the following:
- 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
This achieves the effect of having three different quote types – or rather, the ability to select between one of the three.
Having created your Segmented Control, you need to link it to the outlet in your class. You can use the same method as before to hook up the control to the quoteOpt property on the View Controller.
You will not need an action event for this control.
Why don’t you build and run to see your new control on screen? It won’t do anything at the moment, but it’s nice to know it’s there!
The Joy of Predicates(判断?)
A predicate is a useful object that filters an array. It’s a bit like having a select with a simple where clause in SQL. I find them quite useful when I have a categorized property list. It saves you from having to create separate property lists.
Don’t hate me, but you have to go back and change quoteButtonTapped: in ViewController.m to use myQuotes instead of movieQuotes, as you will soon do something quite different for your movie quotes. And you need to put a condition around it, so that you’ll only use this when the third option is selected in the Segmented Control.
Or, if you prefer, simply replace quoteButtonTapped: with the following code:
-(IBAction)quoteButtonTapped:(id)sender { // 1 - Get personal quotes when the final segment is selected if (self.quoteOpt.selectedSegmentIndex == 2) { // 1 - Get number of rows in array int array_tot = [self.myQuotes count]; // 2 - Get random index int index = (arc4random() % array_tot); // 3 - Get the quote string for the index NSString *my_quote = self.myQuotes[index]; // 4 - Display the quote in the text view self.quoteText.text = [NSString stringWithFormat:@"Quote:\n\n%@", my_quote]; } } |
Now the user will see myQuotes only when they select the third option. As you’ll notice the rest of the code is the same as before, the only difference is that you display a quote (and that quote comes from the personal quote list) only when the segmented control has segment with index 2 selected. And as you might recall, since the segment control starts at index 0, index 2 means the third item.
Build and test your code to make sure that it works as you expect and that the quotes show up only when the “Mine” tab/segment is selected.
For the predicate fun, first you figure out the category you need based on the selected segment control and then use the category to create a filtered array of quotes that matches the category. Stay with me!
This is the code on the other side of the if statement in quoteButtonTapped: – so simply add this to the end of the method to complete the “if” statement begun in section #1:
// 2 - Get movie quotes else { // 2.1 - determine category NSString *selectedCategory = @"classic"; if (self.quoteOpt.selectedSegmentIndex == 1) { selectedCategory = @"modern"; } // 2.2 - filter array by category using predicate NSPredicate *predicate = [NSPredicate predicateWithFormat:@"category == %@", selectedCategory]; NSArray *filteredArray = [self.movieQuotes filteredArrayUsingPredicate:predicate]; // 2.3 - get total number in filtered array int array_tot = [filteredArray count]; // 2.4 - as a safeguard only get quote when the array has rows in it if (array_tot > 0) { // 2.5 - get random index int index = (arc4random() % array_tot); // 2.6 - get the quote string for the index NSString *quote = filteredArray[index][@"quote"]; self.quoteText.text = [NSString stringWithFormat:@"Movie Quote:\n\n%@", quote]; } else { self.quoteText.text = [NSString stringWithFormat:@"No quotes to display."]; } } |
Okay, build and run. Check that you see the right type of quote depending on your selection. If you are always getting the same type, my guess would be that you may not have linked the Segmented Control to your class.
The String Symphony
So far, so good! Now let’s explore some different string options and syntax in Objective-C.
If the quote has a source in the property list, then the app should display that as well. To check if there’s a value in a string, you can check the length of the string.
So add the following to quoteButtonTapped: after the first line in section #2.6 (the first line not counting the comment, that is):
// 2.7 - Check if there is a source NSString *source = [[filteredArray objectAtIndex:index] valueForKey:@"source"]; if (![source length] == 0) { quote = [NSString stringWithFormat:@"%@\n\n(%@)", quote, source]; } // 2.8 - Set display string |
Also, comment out this line, you won’t need it anymore:
//self.quoteText.text = [NSString stringWithFormat:@"Movie Quote:\n\n%@", quote];
|
You get the source from the array and check that it contains a value by making sure that its length is not zero. ! represents NOT. Use == when checking if an integer is equal to a value.
Then you build a new display string by combining the quote and the source using stringWithFormat.
To make things more interesting, why don’t you display something slightly different for quotes from classic movies that will involve checking the value of the category of the selected quote?
Replace section #2.8 in quoteButtonTapped: with the following:
// 2.8 - Customize quote based on category if ([selectedCategory isEqualToString:@"classic"]) { quote = [NSString stringWithFormat:@"From Classic Movie\n\n%@", quote]; } else { quote = [NSString stringWithFormat:@"Movie Quote:\n\n%@", quote]; } // 2.9 - Display quote self.quoteText.text = quote; |
This checks if the string is equal to a specific value, in this case “classic”, and customizes the label for the quote based on the category.
If you want to check for a particular movie title (or for that matter any other string attribute) starts with a particular value, you can do that too. Say you want to display some extra text if the quote is from a Harry Potter movie – add the following right above section #2.9:
if ([source hasPrefix:@"Harry"]) { quote = [NSString stringWithFormat:@"HARRY ROCKS!!\n\n%@", quote]; } |
As you can guess, hasPrefix is used to check if the start of the string has a particular text value.
Build and run your app to make sure that it works as you expect it to. Pay attention to the different categories and to Harry Potter movie quotes to make sure that it all works correctly.
It’s ARRAYning Quotes
Just for fun, you’re going to concatenate all your quotes together, as there aren’t many. This will illustrate how to loop through an array, which you might need to do if you want to iterate over each item in an array to process each item.
In quoteButtonTapped: replace section #1 with the following:
if (self.quoteOpt.selectedSegmentIndex == 2) { // 1.1 - Get array count int array_tot = [self.myQuotes count]; // 1.2 - Initialize string for concatenated quotes NSString *all_my_quotes = @""; NSString *my_quote = nil; // 1.3 - Iterate through array for (int x=0; x < array_tot; x++) { my_quote = self.myQuotes[x]; all_my_quotes = [NSString stringWithFormat:@"%@\n%@\n", all_my_quotes,my_quote]; } self.quoteText.text = [NSString stringWithFormat:@"%@", all_my_quotes]; } |
A for loop is used to loop through the array from row 0 to the last row. x is the counter that is used to keep track of the rows.
Now run and check the results.
One last thing. I mentioned earlier that there are different types of arrays: NSArray andNSMutableArray. So far, each has done the same job in this project.
Use an NSMutableArray if you want to modify/update it. As the name implies, a mutable array can be changed whereas a normal NSArray is static and you cannot add new items to the array or delete items from the array easily.
For example, if you wanted to update the array after a row had been selected in order to show that that quote had already been displayed, you would need an NSMutableArray.
In this project, movieQuotes is your NSMutableArray. You’re using a predicate, so you first need to find the row in movieQuotes and then update it. Add the following code after section #2.9 inquoteButtonTapped::
// 2.10 - Update row to indicate that it has been displayed int movie_array_tot = [self.movieQuotes count]; NSString *quote1 = filteredArray[index][@"quote"]; for (int x=0; x < movie_array_tot; x++) { NSString *quote2 = self.movieQuotes[x][@"quote"]; if ([quote1 isEqualToString:quote2]) { NSMutableDictionary *itemAtIndex = (NSMutableDictionary *)self.movieQuotes[x]; itemAtIndex[@"source"] = @"DONE"; } } |
You loop through the array and check each row to see if it’s the row you’re looking for. Again you use isEqualToString; this time, however, you’re comparing two string variables.
To update the row in the array, you retrieve the object for the row in question and update the object. This is starting to get a bit advanced for this tutorial, but it’s useful to know.
Since you’re updating the source string and since the source string is what is used to select quotes for each category, the row will not be included next time you use NSPredicate to filter the array. And that’s quite neat.
Where to Go From Here?
Here is a sample project with all of the code from the above tutorial.
Well, you’ve reached the end of this little project. You’ve learned how to use arrays in different ways, initiate actions from interface events, access objects via Interface Builder using a XIB file and do various string comparisons and concatenations. That is not a bad effort if this is your first iPhone app!
Now that you know the basics, you might want to try out our How To Create a Simple iPhone App tutorial series, or sign up for our monthly newsletter for an epic-length iOS tutorial for beginners.
The forums are available if you have any questions about what you’ve done. Also, if you liked this tutorial and would like to see more in the series, please let me know in the forums!
In the meantime, good luck and keep ‘appy! :]