tag:blogger.com,1999:blog-39700475691055961052024-02-19T08:33:25.847+01:00Humble Coder"When one teaches, two learn..."<br>
A blog about iPhone development.<br>
(Trace amounts of Java and JavaScript might be found.)henrikbhttp://www.blogger.com/profile/05536290915355204969noreply@blogger.comBlogger24125tag:blogger.com,1999:blog-3970047569105596105.post-60852217030445834512010-08-31T22:12:00.006+02:002010-08-31T22:28:12.449+02:00RussPack LITE for iPhone<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgH0jKkBnbg7b3Yaef66R48g-s3EJPYDwQQOXVydBzUVTqdK6N0s9xcIEkuYCtHiTcAG3S442Wr2mESYRmfwItYEGRG8SV4eNsCJdBHzc4z6Etp9QakvqQCZyoodj1K6M_xngtQQvspJ9g/s1600/screenshot-main-menu.png"><img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;width: 214px; height: 320px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgH0jKkBnbg7b3Yaef66R48g-s3EJPYDwQQOXVydBzUVTqdK6N0s9xcIEkuYCtHiTcAG3S442Wr2mESYRmfwItYEGRG8SV4eNsCJdBHzc4z6Etp9QakvqQCZyoodj1K6M_xngtQQvspJ9g/s320/screenshot-main-menu.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5511670040693195858" /></a><br /><a href="http://phobos.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=389330434&;amp;amp;amp;amp;mt=8">RussPack LITE</a> is now available for all of you who want to try the game for free before you buy the <a href="http://phobos.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=385917699&;amp;amp;amp;amp;mt=8">full version</a> :) To find out more about RussPack, please visit the <a href="http://russpack.com/">web site</a>.<br /><br />I decided to add iAds to this edition which was surprisingly easy. Since this subject is covered quite well in many other blog posts, I decided not to write a post about here in my blog.<br /><div><br /></div><div>I used information from <a href="http://www.raywenderlich.com/1371/how-to-integrate-iad-into-your-iphone-app">Ray Wenderlich's post</a> and the high quality feedback he got through comments from his readers to enable iAds in RussPack LITE. I chose a much simpler path than he did though, since I just added a ADBannerView "on top" of some of my menu screens and the get ready screen which pops up before each level.</div><div><br /></div><div>I decided not to put iAds on the actual game screen for two reasons; a) it would have required a redesign of the layout, which would have been very hard since I already had used up all screen estate and b) it would have been quite annoying for the player :)</div><div><br /></div><div>I should also mention that I updated both RussPack and RussPack LITE to version 1.1 because of an embarassing bug I found and becase the game was listed in AppStore as requiring iOS 4.0+ even though I had not used any 4.0 specific features. It turned out this was my own fault since you have to be careful to set the "deployment target" correctly before submitting the game to AppStore. This is in fact also covered in Ray's post that I mentioned above so go pay him a visit right away to avoid making the same mistake I did.</div><div><br /></div><div>Go ahead and download the free version of the game now - I'll promise you that it's worth a test drive :)</div><div><br /></div>henrikbhttp://www.blogger.com/profile/05536290915355204969noreply@blogger.com0tag:blogger.com,1999:blog-3970047569105596105.post-43117464954033502642010-08-14T01:46:00.007+02:002010-08-31T22:27:54.945+02:00RussPack for iPhone<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEizD-sFWhBCsNkOdIxh21nUD8YGrffuT4Pe1Rw_XKGZ7QIzTyNzFHBFXu-LkaXRMnPM27OTuMx7yke-av-4oz8fWJ-R7dEO4fMJtJppQAlY4Sq4DZxAemVYyZmCkI8tdbSfHHyQIVNOEkA/s1600/screenshot-main-menu.png"><img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;width: 214px; height: 320px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEizD-sFWhBCsNkOdIxh21nUD8YGrffuT4Pe1Rw_XKGZ7QIzTyNzFHBFXu-LkaXRMnPM27OTuMx7yke-av-4oz8fWJ-R7dEO4fMJtJppQAlY4Sq4DZxAemVYyZmCkI8tdbSfHHyQIVNOEkA/s320/screenshot-main-menu.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5505046836373224850" /></a><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjV8mI0uhC-SCBLlHNE1Urj7MEtv4cMJbKd_Nba-1Uc_-VIRgCJDq1dhJxoYdYcSo2hyphenhyphenYCLeLki41fDCNoyjb3mhKpOwk67EWYN1hyG9dZ4GBjnGY67CUkePwbDJV707pvqpLzHpnsv69g/s1600/screenshot-ingame-level4.png"><img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;width: 214px; height: 320px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjV8mI0uhC-SCBLlHNE1Urj7MEtv4cMJbKd_Nba-1Uc_-VIRgCJDq1dhJxoYdYcSo2hyphenhyphenYCLeLki41fDCNoyjb3mhKpOwk67EWYN1hyG9dZ4GBjnGY67CUkePwbDJV707pvqpLzHpnsv69g/s320/screenshot-ingame-level4.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5505046730651999346" /></a><br />My first iPhone app is finally available on AppStore. It's a puzzle game called RussPack and it demonstrates many of the techniques explained in this blog. So if you need some motivation in order to learn how to develop for the iPhone I suggest you check it out! :)<br /><br />If you have had any use of the information in this blog, I would appreciate highly if you bought RussPack from AppStore, as a small token of appreciation.<br /><br />See <a href="http://russpack.com/">www.russpack.com</a> for more information about the game and how to buy it.<br /><br />If anyone wonders why it took so long for me to release my first app - almost 1,5 years - it's beacause I got a little baby boy at almost the same time as I started learning how to develop for the iPhone. Having a baby leaves little time for fun stuff like blogging and coding, but as you can see I managed to find some time. Mostly on the bus and subway on the way to my work actually ;)<br /><br />Thanks for your attention and hope you like the game - it's actually really fun and addictive!<br /><br /><br /><object width="425" height="344"><param name="movie" value="http://www.youtube.com/v/HIPinXeElYA&hl=en&fs=1"><param name="allowFullScreen" value="true"><param name="allowscriptaccess" value="always"><embed src="http://www.youtube.com/v/HIPinXeElYA&hl=en&fs=1" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="344"></embed></object>henrikbhttp://www.blogger.com/profile/05536290915355204969noreply@blogger.com0tag:blogger.com,1999:blog-3970047569105596105.post-77386861234401156042010-03-12T09:24:00.007+01:002010-03-12T10:27:14.834+01:00Revisited: Storing and retrieving information using plistsQuite a while back I wrote <a href="http://humblecoder.blogspot.com/2009/05/iphone-tutorial-storing-and-retrieving.html">a post</a> about storing and retrieving information using plists. This has turned out to be one of the most appreciated posts in my blog, both by the number of readers and the number of comments. My post also contained some errors, but friendly commenters have helped eachother out in order to resolve those errors.<br /><br />When I wrote the original post I hadn't really resolved how to store information on a real device in the proper way. For example, if you have a file in your application bundle which you want to update, you should start by copying the file from the bundle to the Documents-directory.<br /><br />To resolve the issues from the first post and show the "proper way" of handling files that are updated by the application I simply created a new Window-based Application and edited the "applicationDidFinishLaunching" method to look like this:<br /><br />- (void)applicationDidFinishLaunching:(UIApplication *)application { <br /><br /> // Override point for customization after application launch<br /> [window makeKeyAndVisible];<br /> <br /> <br /> // get the path to the "Documents" directory<br /> NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);<br /> NSString *documentsDirectory = [paths objectAtIndex:0];<br /> <br /> // get the path to our plist ("Documents/foo.plist")<br /> NSString *plistPath = [documentsDirectory stringByAppendingPathComponent:@"foo.plist"];<br /><br /> // read or create plist<br /> <br /> NSMutableDictionary *dict;<br /> // check if our plist already exists in the Documents directory...<br /> NSFileManager *fileManager = [NSFileManager defaultManager];<br /> if ( [fileManager fileExistsAtPath:plistPath] ) {<br /> // ...if it does, read it<br /> NSLog(@"dict existed, reading %@", plistPath);<br /> dict = [NSMutableDictionary dictionaryWithContentsOfFile:plistPath];<br /> } else {<br /> // ...if it doesn't, create it<br /> NSLog(@"dict didn't exist, creating...");<br /> dict = [NSMutableDictionary dictionaryWithCapacity:1];<br /><br /> // Fill the dictionary with default values, either by copying<br /> // a default plist from our bundle to the Documents directory<br /> // or simply creating a new dictionary and writing it to th<br /> // Documents directory. Here we choose to create a new<br /> // dictionary rather than providing a default plist in the bundle.<br /> <br /> // create a NSNumber object containing the<br /> // integer value 1 and add it as 'key1' to the dictionary.<br /> NSNumber *number = [NSNumber numberWithInt:1];<br /> [dict setObject:number forKey:@"key1"];<br /><br /> // write dictionary to Documents directory...<br /> NSLog(@"writing to %@...", plistPath);<br /> [dict writeToFile:plistPath atomically:YES];<br /> }<br /><br /> // dump the contents of the dictionary to the console<br /> NSLog(@"dumping...");<br /> for (id key in dict) {<br /> NSLog(@"key=%@, value=%@", key, [dict objectForKey:key]);<br /> }<br /> <br /> // check if key2 is present<br /> NSString *value2 = (NSString *)[dict valueForKey:@"key2"];<br /> if ( value2 == nil ) {<br /> NSLog(@"key2 didn't exist, adding...");<br /> [dict setObject:@"default-2" forKey:@"key2"];<br /> }<br /><br /> // dump the contents of the dictionary to the console<br /> NSLog(@"dumping...");<br /> for (id key in dict) {<br /> NSLog(@"key=%@, value=%@", key, [dict objectForKey:key]);<br /> }<br />}<br /><br />The code should be rather self-explanatory thanks to the inline comments, but I really recommend you to read my <a href="http://humblecoder.blogspot.com/2009/05/iphone-tutorial-storing-and-retrieving.html">first post</a> on this subject to really understand what is going on.<br /><br />A thing worth commenting on is the really strange path names logged to the console. These pathnames can look like this:<br /><br />2010-03-12 09:09:15.557 Plist2[10780:20b] writing to /Users/henrik/Library/Application Support/iPhone Simulator/User/Applications/E10D7186-1219-4C52-804A-FE217F760967/Documents/foo.plist...<br /><br />This path is a path in the Mac OS filesystem used by the iPhone simulator when testing an application. The strange part of it is the long string of hexadecimal numbers starting with E10D7. This is the so called GUID which is unique for each installed application. A lot has been written about this in other sources on the Internet, so I won't go further into that:<br /><br /><a href="http://trailsinthesand.com/where-is-the-iphone-simulator-filesystem-stored/"></a><br /><a href="http://richard.giliam.net/?p=13"></a><br /><a href="http://stackoverflow.com/questions/271577/how-to-keep-the-iphone-simulator-application-directory-be-the-same-when-run-it-ev"></a><br /><br />One annoying thing about it and that is that it changes each time you recompile your application. That means, that each time you build and test your application the files created by the application will be somewhere else in the Mac OS filesystem. This makes it hard to inspect the files written by your application. Fortunately, modern versions of XCode copy the contents from the "old path" to the "new path" so even though the GUID changes, the files created by the application the last time are available the next time you build and test it.<br /><br />If you couple this annoying thing with the fact that nothing is logged to the console if you exit your application by pressing the home-button in the simulator and then relaunch the application by pressing its icon in the simulator. This means that if you want to see what is logged to the console you are forced to relaunch the application from XCode, which will create a new GUID and thus a new path in the Mac OS filesystem.<br /><br />However, since XCode nowadays copies the contents from the old to the new path it is possible to test how the application responds to "external changes" such as removing a file stored by the application, etc. This can be very convenient to perform effecient testing of an application.<br /><br />For the application in this post you can test how it behaves if "foo.plist" is present or not by first running the application from XCode (CMD-Return), observing which path is logged to console, removing that file and then re-running the application from XCode (CMD-Return). This will give you something like this on the console:<br /><br />[Session started at 2010-03-12 10:18:24 +0100.]<br />2010-03-12 10:18:27.456 Plist2[10888:20b] dict existed, reading /Users/henrik/Library/Application Support/iPhone Simulator/User/Applications/DB3B1A75-BF08-461E-919A-DBFFCC994036/Documents/foo.plist<br />2010-03-12 10:18:27.464 Plist2[10888:20b] dumping...<br />2010-03-12 10:18:27.468 Plist2[10888:20b] key=key1, value=1<br />2010-03-12 10:18:27.469 Plist2[10888:20b] key2 didn't exist, adding...<br />2010-03-12 10:18:27.471 Plist2[10888:20b] dumping...<br />2010-03-12 10:18:27.475 Plist2[10888:20b] key=key1, value=1<br />2010-03-12 10:18:27.477 Plist2[10888:20b] key=key2, value=default-2<br /><br />[Session started at 2010-03-12 10:19:38 +0100.]<br />2010-03-12 10:19:40.159 Plist2[10897:20b] dict didn't exist, creating...<br />2010-03-12 10:19:40.180 Plist2[10897:20b] writing to /Users/henrik/Library/Application Support/iPhone Simulator/User/Applications/5B1E8FF3-45D6-443B-BB0B-2073EAE22520/Documents/foo.plist...<br />2010-03-12 10:19:40.191 Plist2[10897:20b] dumping...<br />2010-03-12 10:19:40.198 Plist2[10897:20b] key=key1, value=1<br />2010-03-12 10:19:40.199 Plist2[10897:20b] key2 didn't exist, adding...<br />2010-03-12 10:19:40.199 Plist2[10897:20b] dumping...<br />2010-03-12 10:19:40.200 Plist2[10897:20b] key=key1, value=1<br />2010-03-12 10:19:40.202 Plist2[10897:20b] key=key2, value=default-2henrikbhttp://www.blogger.com/profile/05536290915355204969noreply@blogger.com3tag:blogger.com,1999:blog-3970047569105596105.post-9617138956774561322009-09-01T17:01:00.003+02:002009-09-01T17:06:08.035+02:00iphone tutorial: Memory management - autorelease and NIB files<p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica">In this tutorial we're going to dive a bit deeper into Cocoa memory management, specifically looking at a feature called <i>autorelease</i>. If you haven't read <a href="http://humblecoder.blogspot.com/2009/08/iphone-tutorial-memory-management.html">the first part</a> of the memory management tutorial, it's highly recommended that you do.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"><span class="Apple-style-span" style="font-size: x-large;">Cocoa memory ownership rules</span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica">In the first part of this tutorial we introduced the concept of memory ownership. To help deciding who actually owns a memory block, the Cocoa designers have developed a set of rules that all Cocoa classes and applications should follow. This set of rules can be found in the "Memory Management Programming Guide for Cocoa":</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica">---</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"><i>To make sure it is clear when you own an object and when you do not, and what responsibilities you have as an owner, Cocoa sets the following policy:</i></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px"><i></i><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"><i>You own any object you create.</i></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"><i>You “create” an object using a method whose name begins with “alloc” or “new” or contains “copy” (for example, alloc, newObject, or mutableCopy).</i></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px"><i></i><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"><i>If you own an object, you are responsible for relinquishing ownership when you have finished with it. You relinquish ownership of an object by sending it a release message or an autorelease message (autorelease is discussed in more detail in “Delayed Release”). In Cocoa terminology, relinquishing ownership of an object is typically referred to as “releasing” an object.</i></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px"><i></i><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"><i>If you do not own an object, you must not release it.</i></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica">---</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica">If you think these rules are too complicated to remember, try to at least remember the naming scheme; methods beginning with "alloc" or "new" or contains "copy" creates an object (and returns a reference to that object) that you are responsible for freeing. That is, these methods, transfer the ownership to you, the caller of the moethod.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica">A new concept is also introduced; autorelease or "delayed release". If you call 'autorelease' on an object reference, that object won't be released immediately, but "later". Before we explain what later means, it might be good to understand why there is such a feature as a "delayed release".</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"><span class="Apple-style-span" style="font-size: x-large;">Autorelease</span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica">Why would you want to release an object <i>later</i> instead of <i>now</i> or <i>not at all</i>? One example is when you have a method that creates objects for someone else than itself.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica">(MyClass.m)</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica">+(MyClass *)createObject {</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"> MyClass *reference = [[MyClass alloc] init];</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"> return reference;</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica">}</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica">The method 'createObject' in the class MyClass allocates and initialises a MyClass object and then returns the reference to the newly created object. An application calling this method</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica">(MyApplication.m)</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica">MyClass *myClass = [MyClass createObject];</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica">would not be responsible for releasing the object since the 'createObject' method name doesn't contain any of the "magic words" listed in the rules above. The 'createObject' method on the other hand calls 'alloc' which is a magic word and is therefore responsible for calling release.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica">It's impossible to call 'release' on the created object after the return statement and 'release' is called before the return statement, the object will be deallocated and the method will return a reference to a deallocated object which will lead to chaos. So when should 'release' be called? Well, "later"...</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica">Here "later" means "after the return statement" and late enough so that the caller of the method gets a chance to store the returned release in a variable (or somewhere else) and call 'retain' on it.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica">This is where the autorelease mechanism steps in and provides a solution. If the method creating the object calls 'autorelease' on the reference instead of 'release' evertyhing will work; the method has fulfilled its responsibility to release the object it created and the caller of the method gets a chance to call retain on the returned object.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica">(MyClass.m)</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica">+(MyClass *)createObject {</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"> MyClass *reference = [[MyClass alloc] init];</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"> [reference autorelease]; // call autorelease on the newly create object</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"> return reference;</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica">}</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica">(MyApplication.m)</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica">MyClass *myClass = [MyClass createObject];</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica">[myClass retain]; // call retain on the returned object to protect it from being deallocated</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica">The autorelease method can be called anywhere the release method should have been called but just as with release it's important to call it the right number of times; for each time 'autorelease' is called 'release' will be called "later".</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"><span class="Apple-style-span" style="font-size: x-large;">NIB files</span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica">We mentioned above that a method that creates an object for someone else is a typical user of the autorelease functionality. Another user of autorelease is a NIB file, or actually the method that load it. A NIB file contains a graph of objects where most objects are connected to each other using outlets. When the loader method rebuilds this object graph in memory, using the NIB file as a blueprint, it creates all objects with a reference count of 1 and then autorelease them.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica">When the connections between the objects are re-established, the loader method calls setter methods of the objects, which should retain the object reference if they want to prevent the newly loaded object from being deallocated. Ownership is transferred to these objects, meaning that the objects also are responsible for releasing the references in their 'dealloc' method. Setter methods for outlets are normally synthesized using the @property/@synthesize mechanism of Objective C.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica">@property (nonatomic, retain) IBOutlet someClass *someOutlet;</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica">The 'retain' keyword of the property declaration will ensure that the corresponding setter method created by the @synthesize keyword will retain the reference passed to it.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica">However, there are also <i>top-level objects</i> in the file that have no natural owner. If you want to keep those objects around, which you normally do, you have to manually retain them (and then later manually releasing them). But how do you get hold of the references to them if they have no "owners"? Well, they do actually get a temporary owner during the loading process; references to all the top-level objects are stored in an array, returned by the 'loadNibNamed:owner:options' method.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica">As a simple alternative to iterating through the array and retain all the objects in it individually you could retain the array itself instead (and then later release it). This works since it prevents the array from being (auto) released and deallocated - if it was it would release all the references stored in it (decreasing the reference count to zero) and thus force all the objects in the array to be deallocated.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"><span class="Apple-tab-span" style="white-space:pre"> </span>NSArray *nibArray = [[NSBundle mainBundle] loadNibNamed:@"Nib1" owner:self options:nil];</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"><span class="Apple-tab-span" style="white-space:pre"> </span>if ( nibArray == nil ) {</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"><span class="Apple-tab-span" style="white-space:pre"> </span>NSLog(@"loadNibNamed failed");</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"><span class="Apple-tab-span" style="white-space:pre"> </span>return;</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"><span class="Apple-tab-span" style="white-space:pre"> </span>}</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"><span class="Apple-tab-span" style="white-space:pre"> </span>[nibArray retain];</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica">NIB files with "unconnected" top-level objects aren't that common since it would be useless to define a lot of objects in a NIB that no one uses. Much more common is that the top-level objects are connected to outlets in the "File's owner" object. When that is the case you don't have to worry about implicitly retaining the objects since they will be retained by the setter methods in the "File's owner".</p><div><span class="Apple-style-span" style="font-family:Helvetica, serif;font-size:100%;"><span class="Apple-style-span" style="font-size:12px;"><br /></span></span></div>henrikbhttp://www.blogger.com/profile/05536290915355204969noreply@blogger.com3tag:blogger.com,1999:blog-3970047569105596105.post-28563016643164264132009-08-10T11:59:00.008+02:002009-08-27T09:06:38.570+02:00iPhone tutorial: Memory management - the basics<div><div><div><div><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">In this tutorial I will try to explain how to manage dynamically allocated memory in Objective C, or more specifically in Cocoa, which is the object oriented programming environment ("API") used for most iPhone applications.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">I will start with a long introduction to general memory management so if you already know everything about that, feel free to skip to the Cocoa specifics. The reason for the long introduction is that it's memory management in general that is complex, not the particulars of Cocoa memory management.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"><span class="Apple-style-span" style="font-size: x-large;">A long introduction to general memory management</span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">When you need a block of memory for something in a computer program you normally <i>allocate</i> it by making a call to a <i>memory manager</i>. The memory manager might be provided by the operating system, a library, a framework, or something similar. When you don't need the allocated block of memory any longer you normally <i>free</i> it - also by calling the memory manager - to make it available for allocation again.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">In most languages with <i>manual</i> memory management, that is, where you, the programmer, has to decide when to allocate and free memory there is one thing you have to remember: free the memory that you allocated! If you forget to free a block of memory, the computer will sooner or later run out of memory, which will cause most programs to stop function properly. Forgetting to free allocated memory is often referred to as <i>leaking</i> memory.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">In simple programs, it's quite easy to decide when to allocate and when to free memory, since these programs usually do things in a linear fashion; you allocate memory, do some processing and then free it.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">memoryBlock = allocateMemory(10); // allocate a 10 byte big memory block</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">process(memoryBlock); // do some processing</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">free(memoryBlock); // free the allocated memory block</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">However, as the complexity of a program increases, you might have to keep the allocated memory around for longer periods of time and the linear chain of events (allocate, process, free) might be broken. Furthermore, you will probably have to allocate multiple blocks of memory and perhaps free them in a different order than the one you allocated them in.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">When you start interacting with code you don't control yourself (libraries, classes, operating systems, frameworks, etc.) things can get complicated even if the underlying principle is very simple (free what you allocated). It can get especially nasty when you allocate a block of memory and then "give it away to someone else" or if you get a memory block from someone. Who should free that memory block and, just as importantly, when should it be freed? To answer those questions, there has to be a way of deciding who <i>owns</i> a block of memory and/or when it is <i>not needed</i> any longer - something we will dive deeper into later in this tutorial.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"><span class="Apple-style-span" style="font-size: large;">Addresses and pointers</span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">When you allocate a memory block from a memory manager you normally get the <i>address</i> of the memory block as the result. The address is simply an offset into the computer's (or program's) memory space, where each byte has a unique address starting from 0 (zero) and increasing upwards.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">Let's say that we have a very simple computer system, with only 256 bytes of memory. That is, there are memory address ranging from 0 to 255. If we start by allocating 10 bytes of memory, you would get the address 0 (zero) as the result. If you then allocate 20 bytes you would get the address 10 (0 + 10) as the result since addresses 0 to 9 are already allocated. Allocating yet another 30 bytes would get the address 30 (10 + 20), and so on.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">(Memory address: Memory block size)</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">0...9: 10 bytes</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">10...29: 20 bytes</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">30...59: 30 bytes</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">If you store the address returned by the memory manager in a variable, that variable is often called a <i>pointer</i>, since it "points to" a memory block. In most languages you normally specify that the variable is going to be used as a pointer, since this will allow you to perform "pointer operations" on that variable. It will also enable sanity and error checks. Furthermore, if you can inform the language what <i>type</i> of information the memory block will contain - a number, a string, a specific data structure, an array of integers, etc. - you often get access to even more operations and error checking.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">Note that if you don't store the address of the allocated memory block in a variable (or somewhere else) it will be impossible the free the memory block, and you will leak memory!</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">It's important to understand that a pointer isn't something magical - it's just a value! This means that you can use pointers anywhere you can use variables and values; passing them as arguments to a function or a method for example. You can also create copies of pointers without any strange side effects - it won't copy the memory block! - all that happens is that you now have two variables pointing to the same memory block.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">pointer = allocate(10); // allocate a 10 bytes big memory block and store the address in a variable called 'pointer'</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">pointerCopy = pointer; // assign the value (address) stored in 'pointer' to a variable called 'pointerCopy'</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">If you want to free the allocated memory block you can do it either by calling free(pointer) or free(pointerCopy) since they both point to the same memory block, but it's very important to free the block only once! Calling both free(pointer) and free(pointerCopy) could have catastrophic consequences! Therefore, once you start copying pointers or passing them as arguments to functions or methods you have to be very careful to ensure that the memory is freed only once!</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"><span class="Apple-style-span" style="font-size: large;">Ownership of dynamically allocated memory</span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">The simplest way of deciding who should free a block of memory and when is to let the owner of the memory block decide it. In simple programs, the code allocating the memory block is the owner, but in more complicated programs ownership of a memory block can be transferred between different parts of the system (library, operating system, framework).</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">In some cases it's obvious who should free the memory block. For example, if you have an arrangement where a producer task allocates memory and hands it over to a consumer task, the consumer will free the memory when it is done with it. The same arrangement is often seen when a program interacts with a library or a framework. The library or framework allocates memory for you, but it's your responsibility to free it. In both these arrangements, ownership of the memory block has been transferred from the one that allocated it to someone else.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">But what happens if the producer would like to keep the memory block around for its own use after passing it to the consumer? Or what if the producer wants to send the same memory block to a logging task as well as to the consumer task? In a situation like this, the consumer and logger are probably not aware of each other and therefore both would think that they would be responsible for freeing the memory block given to them by the producer.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">producer: pointer = allocateMemory(10)</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">producer: process(pointer)</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">producer: sendToConsumer(pointer)</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">producer: sendToLogging(pointer)</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">...</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">logger: pointer = getMemoryBlockFromProducer()</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">logger: process(pointer)</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">logger: free(pointer)</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">...</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">consumer: pointer = getMemoryBlockFromProducer()</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">consumer: process(pointer)</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">consumer: free(pointer)</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">...</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">If the logger is executed before the consumer, like in the scenario above, the memory block would already be freed by the logger once the consumer gets it. This is catastrophic since anything could have happened to that memory block after it was freed. The worst scenario is that someone else could have allocated the same memory block and changed the contents of it.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">The problem here is that it is not clear who owns the memory block - the producer, consumer or logger - and therefore it is not clear who should free it. In fact, one can say that there are multiple owners of the memory block!</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"><span class="Apple-style-span" style="font-size: large;">Copying memory to avoid ownership problems</span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">A simple way of avoiding ownership problems is to ensure that each memory block only has one owner and that that owner is responsible for freeing it. This can be achieved by allocating a new memory block, copy the contents of the original block into the new block and then hand over the copy to the one needing it.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">producer: pointer = allocateMemory(10) // allocate a 10 bytes memory block</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">producer: process(pointer) // do some processing</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">producer: pointer2 = allocateMemory(10) // allocate a second 10 bytes memory block</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">producer: copyMemory(pointer2, pointer) // copy the contents of the first memory block into the second memory block</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">producer: sendToConsumer(pointer) // send the second memory block to the consumer</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">producer: pointer3 = allocateMemory(10) // allocate a third 10 bytes memory block</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">producer: copyMemory(pointer3, pointer) // copy the contents of the first memory block into the third memory block</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">producer: sendToLogging(pointer3) // send the third memory block to the logger</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">...</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">producer: free(pointer) // free the first memory block since we are the owner of it</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">The consumer and logger both free their respective memory blocks once they are done with it. This works very well, but there are two things to consider; more memory is used since we make a lot of copies and a lot of time is wasted with copying the information from the original block into the copy, which will make the program run slower. On modern computers with plenty of memory and fast CPUs, this is probably no catastrophe, but still a little wasteful. On the other hand, the wastefulness might be a cheap price to pay for avoiding memory leaks or other nasty memory related problems!</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">Another thing to consider is that the information in the memory blocks (the original and the copies) start "living their own lives", that is, when the information in the original block is changed the information in the copies remains unchanged and vice versa. In some situations this doesn't matter or might even be required, but in others it is catastrophic. The computer doesn't know which behaviour you want/require, so you as the programmer need to inform the computer by writing code that produces the required behaviour.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"><span class="Apple-style-span" style="font-size: medium;">Copying memory that contains pointers</span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">We have previously hinted that a pointer doesn't have to be stored in a variable - it can also be stored "somewhere else". This other place is normally in a memory block. We have emphasized that a pointer is just a value, but we haven't mentioned anything about what type of value it is. It's easiest to consider a pointer to be a positive integer with a value between 0 (zero) and some maximum value. The simple computer system used above only has 256 bytes of memory, that is addresses between 0 and 255, which means that the maximum value for a pointer in this system is 255. This, in turn, means that a pointer can be represented using a single byte.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"><span class="Apple-style-span" style="font-size: medium;">Deep and shallow copies</span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">Let's say we have the following arrangement. We allocate three 1-byte memory blocks, starting at address 50 and fill the blocks with the integers 1, 2 and 3 respectively. After that we allocate a 3-byte memory block at address 53 (50 + 3) and fill the block with the values 50, 51 and 52.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">(Address: Memory block size: Contents)</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">50: 1 bytes: 1</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">51: 1 bytes: 2</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">52: 1 bytes: 3</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">53: 3 bytes: 50, 51, 52</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">What we have created in the 3-byte memory block is effectively an array of integers, but instead of containing the integers themselves, the array contains pointers to the integers.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">What if we want to make a copy of this array? If the array had contained the integers themselves, there wouldn't be much to think about; just allocate a second 3-byte memory block and copy the contents of the first 3-byte memory block into the second. But now the array contains pointers to the integers instead of integers, so if we try the simple copy just described we would end up with the following:</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">(Address: Memory block size: Contents)</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">50: 1 bytes: 1</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">51: 1 bytes: 2</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">52: 1 bytes: 3</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">53: 3 bytes: 50, 51, 52</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">56: 3 bytes: 50, 51, 52</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">What happens is that we have copied the pointers. Nothing else. This is similar to what we did above when we copied a variable containing a pointer. Just as above, we now have two pointers pointing to the same memory block. If we change the integer at address 50 from 1 to 10, this change will be "seen" by both the original array and the copy.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">Sometimes this is what one needs or requires, but sometimes a "real" - fully independent - copy is required, that is, a copy which won't be affected by changes in the original.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">To accomplish this, we need to copy the three 1-byte memory blocks instead of the 3-byte memory block containing the pointers. That is, we have to allocate three new 1-byte memory block and copy the contents of the first three 1-byte memory blocks into the three new ones. After that we have to allocate a second 3-byte memory block and put the addresses of the three new 1-byte memory blocks in it.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">(Address: Memory block size: Contents)</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">50: 1 bytes: 1</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">51: 1 bytes: 2</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">52: 1 bytes: 3</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">53: 3 bytes: 50, 51, 52</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">56: 1 bytes: 1</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">57: 1 bytes: 2</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">58: 1 bytes: 3</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">59: 3 bytes: 56, 57, 58</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">Now we have made a truly indepent copy of the array. This is called making a <i>deep copy</i>. Just copying the pointers as we did initially is called making a <i>shallow copy</i>. If we now change the integer at address 50 from 1 to 10, this will only be "seen" by the original array, not the copy.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">As mentioned above, the decision to make a deep or shallow copy can only be made by the programmer, since the computer doesn't know what kind of behaviour your require.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"><span class="Apple-style-span" style="font-size: large;">Reference counting</span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">As we have mentioned before, it's quite common to get into situations where a memory block has several owners or users. We have also mentioned that it's hard to decide when and who of the owners that should free such a block. A technique called <i>reference counting</i> solves both these problems. Provided that all the owners play by the same rules, that is.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">A simple set of rules that is quite easy to follow and also quite easy to implement in the memory manager is a technique called reference counting. As the name implies, the technique works by counting how many references (pointers) there are to a certain memory block, that is how many owners the block has.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">When the memory block is first allocated, by the first owner, the counter is set to 1. If the first user "gives away" the block to someone else, for example by passing it to another task, the counter is increased by one 1 to signal that there now are two owners of the block - or two references to it.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">When any of the owners frees the block, the counter is decreased and when it reaches zero it is freed. If the counter is greater than zero, nothing happens! This means that the memory block is "kept around" until all users are done with it - very simple and very efficient!</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">In normal memory management all you do is allocate memory, passing and copying pointers, and freeing memory. The only thing that is different from "normal" memory management when you use reference counting is that someone actively has to increase the reference counter. Also, you won't be able to call the normal free function when you're done with a memory block; you instead have to call a special kind of free that decreases the reference counter and don't call the normal free until the counter reaches zero.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">To summarise, when you use reference counting you have to inform the computer when you want to add an owner (increase the counter) or remove an owner (decrease the counter) to a memory block. When it's appropriate to do this is completely up to the programmer to decide since the computer has no way of knowing what type of behaviour you want, and that is where the complexity of reference counting lies - calling the add/remove owner functions at the right time and place.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">So how could reference counting be implemented by the memory manager? A very simple solution is to allocate a little more memory each time someone requests memory and use this extra memory to hold the reference counter for the memory block. In our simple 256-byte computer, this could mean allocating one extra byte for each memory block. If some requests 3 bytes of memory, the memory manager actually returns 4 bytes of memory but instead of returning the address to the first byte in the memory block, it returns the address to the second byte.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">50: 1 (this address holds the reference counter and is initialised to 1 by the memory manager)</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">51: ??? (this is the address returned by the memory manager)</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">52: ???</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">53: ???</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">When someone calls the "remove owner" function on the memory block above, the argument to the function will be a pointer containing the value 51 (not 50!). However, the function knows that there actually is an extra byte stored before the memory block, so it decreases the value stored in the extra byte by one and checks if it becomes zero (no more owners left). If it reaches zero, the 4-byte memory block is freed "for real."</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">The best thing with this very simple solution is that it can be implemented "on top of" a normal, non-reference counting, memory manager. This makes it possible to write a reference counting memory manager in a library (or even in the application itself!). After that you could start writing applications that use the library and these applications would then get support for reference couting memory management even though the underlying memory manager (for example an operating system) doesn't support it.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">So how big an impact does reference counting have on the code that you write? Do you have to start thinking in completely new ways to use it properly? Well, you do have to start using at least two new function calls; one for adding an owner to a memory block and one to remove an owner from a memory block. You also have to starting thinking a little differently since you need to call these two functions at appropriate places in the code.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">In an example above (in the section about copying memory blocks to avoid ownership problems), we had the producer copy a memory block before giving it to the consumer and logger respectively. Could we have made it the "other way round", that is, having the consumer and logger copy the block given to them? Yes, that would have worked just as well! Who should make the copy is just a matter of perspective, that is, who should be aware of the ownership problems - the giver or the receiver?</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">The same reasoning applies to reference counting, but instead of copying memory blocks an owner is added to a memory block. Adding an owner can be made either by the giver or the receiver. However, often it's "nicer" or more convenient to have the receiver add itself as an owner instead of having the giver add the receiver as an owner. Later, when the receiver is done with the memory block it can remove itself as an owner. That way, the memory management issues can be completely "contained" in the receiver (application, class, library, framework, etc.).</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"><span class="Apple-style-span" style="font-size: x-large;">Objective C memory management</span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">In object oriented languages like Objective C, memory is usually allocated in order to create an object. Therefore, one usually talks about objects instead of memory blocks and references to objects instead of pointers even though a reference is nothing else but a variable containing the address of an object. Just as with pointers, references are normally "typed" to inform the language what type of object that is "referenced", but there is also a general reference type called 'id' which can reference any type of object.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">As with all posts in this blog, remember that I am writing about stuff I have recently begun to understand myself and this is particularly true for the memory management parts of Objective C and Cocoa. That said, as far as I understand, Objective C doesn't have any standardised memory management scheme apart from functions malloc() and free from the standard C library.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"><span class="Apple-style-span" style="font-size: x-large;">Cocoa memory management</span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">Cocoa can be explained as a "programming environment", API or class library intended to radically simplify the development of complex (graphical) applications. In this tutorial, we're going to focus on what it has to offer when it comes to memory management.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">Cocoa uses reference counting to manage memory ownership problems and it's usually the receiver of a reference that "adds itself as an owner" to an object. In Cocoa, the reference counting scheme is accessed through the Cocoa root class NSObject, or actually the NSObject protocol. Since almost all objects in Cocoa implement this protocol, the reference counting scheme is available to "everyone". Furthermore, a set of rules have been developed by the Cocoa developers, meaning that almost all Cocoa classes are compatible with each other with regard to memory management. These rules also simplifies the life for the application developer (you!).</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"><span class="Apple-style-span" style="font-size: large;">Allocating an object</span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">When you want to allocate memory for an object you call the 'alloc' method of the class.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> MyClass *myClass = [MyClass alloc];</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">This line of code first declares an object reference called 'myClass' of the type 'MyClass *'. The asterisk (*) is a syntax inherited from the C language, where it declares a variable as a pointer. In this case a pointer to an object of the type MyClass.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">[MyClass alloc] sends the message/calls the method 'alloc' of the class MyClass, which allocates a sufficient amount of memory for an object of the type MyClass. It also sets the reference counter of the memory block used for the object to 1 (one) to indicate that there now is one owner associated with the memory - the one calling 'alloc'.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"><span class="Apple-style-span" style="font-size: large;">Initialising an object</span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">Before an object can be used it has to be initialised, which normally is done right after memory has been allocated for the object. Initialising an object puts it into a known initial state; assigning default values to member variables, etc. You do this by calling the 'init' method of the NSObject protocol.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> MyClass *myClass = [MyClass alloc];</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> [myClass init];</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">Note that 'init' is called on the object 'myClass' and not the class 'MyClass', that is, it uses the an object/instance of the type MyClass rather than the class MyClass itself.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">Init also returns an object reference (pointer) and since an 'init' method is allowed to return another object than the one it was called on, it's very important to use the pointer returned by init:</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> MyClass *myClass = [MyClass alloc];</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> myClass = [myClass init];</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">Since both 'alloc' and 'init' return an object reference and you have to use the one returned by 'init', it's often more convenient to use "cascading" method calls to allocate and initialise an object with a single statement.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> MyClass *myClass = [[MyClass alloc] init];</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"><span class="Apple-style-span" style="font-size: large;">Freeing an object</span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">When you are done with an object you need to free it, or actually "remove yourself as an owner" as Cocoa uses reference counting rather than manual memory management. You do this by calling the 'release' method of the NSObject protocol.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> [myClass release];</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">This method decreases the reference counter of the memory associated with the object and if the counter reaches zero, the memory is freed. However, before the memory is freed, the 'dealloc' method of the class is called to make it possible for the object to "clean up" before it's freed. Cleaning up basically boils down to releasing all references held by the object. If this step is skipped, memory will leak since no one will take care of releasing the references held by the freed object since it no longer exists!</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"><span class="Apple-style-span" style="font-size: large;">Adding an owner to an object</span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">When an object should be shared/used by multiple users, the additional users need to add themselves as owners. This is done by calling the 'retain' method of the NSObject protocol.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> MyClass *myClass = [[MyClass alloc] init]; // allocate and initialise an object</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> [myClass retain]; // add an owner (increase the reference counter)</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> MyClass *myClassReferenceCopy = myClass; // copy the reference</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">Now the same object has two references; myClass and myClassReferenceCopy. Furthermore, the object has a reference count of two indicating that it has two owners (or references). Conveniently, 'retain' returns a reference to the object, which makes it possible to perform the operations above in two statements instead of three.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> MyClass *myClass = [[MyClass alloc] init]; // allocate and initialise an object</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> MyClass *myClassReferenceCopy = [myClass retain]; // retain the object and copy the reference</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"><span class="Apple-style-span" style="font-size: large;">Balancing retain and release</span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">A very important rule with reference counting is to balance the addition and removal of ownership, or in Cocoa terms, balancing the calls to retain and release. Balance means that for each call to 'retain' a call to 'release' is required. If this balance isn't maintained, the reference counter of some object will never reach zero and thus will never be deallocated. This effectively creates a memory leak!</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"><span class="Apple-style-span" style="font-size: large;">Setter methods</span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">One of the most common scenarios for calling 'retain' on a reference is in a "setter" method of a class, that is, when a user of an object wants to assign a value to one of the objects member variables. For example, say that we have two classes A and B, where objects of type A contains a reference to an object of type B.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> (ClassA.h)</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> @interface ClassA : NSObject {</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> <span class="Apple-tab-span" style="white-space:pre"> </span>ClassB *classB;</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> }</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> -(void)setClassB:(ClassB *)reference;</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">Here we see that there is a member variable called 'classB' which is a reference to an object of type ClassB. If an application allocates and initialises one instance of ClassA and one instance of ClassB and then call 'setClassB' on A, the reference to the ClassB object will be "given to" the ClassA object.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> (MyApplication.m)</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> ClassA *classA = [[ClassA alloc] init];</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> ClassB *classB = [[ClassB alloc] init];</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> [classA setClassB:classB];</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">A naive implementation of a setter method which simply assigns the value of the supplied parameter to the member variable</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> (ClassA.m)</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> -(void)setClassB:(ClassB *)reference {</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> classB = reference; // store the new reference in a member variable</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> }</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">does indeed work in simple situations, but relies on the application not to release the reference it supplied as a paramter to the setter method. If it does, the ClassB object will be deallocated and the ClassA object will have a reference to a deallocated object, which is a catastrophe!</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">The ClassA object needs to be sure that the ClassB object it is using doesn't get deallocated and to guarantee this it needs to add itself as an owner.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">A somewhat less naive implementation is to retain the reference passed as an argument</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> (ClassA.m)</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> -(void)setClassB:(ClassB *)reference {</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> [reference retain]; // retain the new reference</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> classB = reference; // store the new reference in a member variable</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> }</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">So, what is wrong with this implemenation? Well, what happens if setClassB is called more than once? It will lead to a situation where the 'retain' calls aren't balanced by 'release' calls. To sort this out, we have to ensure the setClassB method maintains the balance.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">A quite good implementation which maintains the balance between retain and release</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> (ClassA.m)</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> -(void)setClassB:(ClassB *)reference {</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> [classB release]; // release the old reference</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> [reference retain]; // retain the new reference</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> classB = reference; // store the new reference in a member variable</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> }</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">So, why is this implementaion just "quite good"? What else needs to be done but balancing the 'retain' and 'release' calls? Well, there is a fringe case that isn't handled by that implementation. A case that can be triggered by calling the setter multiple times with the same reference! Doing this could lead to a situation where the object referenced by the 'reference' parameter is deallocated before 'retain' is called on it in the setter.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> (MyApplication.m)</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> ClassA *classA = [[ClassA alloc] init]; // classA reference count == 1</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> ClassB *classB = [[ClassB alloc] init]; // classB reference count == 1</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> [classA setClassB:classB];</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> [classB release]; // release the old reference (nil; nothing happens)</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> [reference retain]; // retain classB; reference count == 2</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> classB = reference; // store classB in a member variable</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> [classA setClassB:classB];</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> [classB release]; // release the old reference (classB; reference count == 1)</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> [reference retain]; // retain classB; classB reference count == 2</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> classB = reference; // store classB in a member variable</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">Hmm, looking at the 'release' calls in 'setClassB' we see that the reference count temporarily reaches 1 before going back to 2 again, but we never see it reaching zero and then be deallocated.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">What saved us this time is that the application still is an owner of the object, and that's why we only reached 1 instead of 0. But doesn't the application has to own the object in order to supply a reference to it as a parameter to 'setClassB'? Not necessarily, all it needs is the reference to the object - it does not have to own it. To protect us from this fringe case we need to reorder the operations in the setter method a bit so that 'retain' is called before 'release'. This will temporarily increase the reference count instead of temporarily decrease it when the setter is called with the same reference multiple times. Temporarily increasing the reference counter is risk free since nothing happens behind the scenes when you do it, in contrast to what can happen when you temporarily decrease.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">A perfect implementation of a setter method</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> (ClassA.m)</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> -(void)setClassB:(ClassB *)reference {</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> [reference retain]; // retain the new reference</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> [classB release]; // release the old reference</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> classB = reference; // store the new reference in a member variable</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> }</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"><span class="Apple-style-span" style="font-size: large;">Getter methods</span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">Getter methods for member variables that are object references can usually be made quite simple since it's customary to let the one calling the getter be responsible for managing the reference counting (calling 'retain' if it wants to keep it around and later, when it is done with it, calling 'release' on it).</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> (ClassA.m)</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> -(ClassB *)getClassB {</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> return classB; // return the reference without calling 'retain' on it</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> }</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> (MyApplication.m)</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> ClassB *classB = [classA getClassB];</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> [classB retain];</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> ...</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> [classB release];</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> </p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"><span class="Apple-style-span" style="font-size: large;">Cleaning up</span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">As we mentioned earlier, if a class has member variables that are references to other objects, objects of that class need to "clean up" before being deallocated. We also mentioned that cleaning up often boils down to releasing references that had previously been retained.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">In Cocoa, the cleaning up method 'dealloc' gets called on an object when it is time to deallocating the object, so if you have any references retained that's the place to do your class specific cleaning up.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">A broken implementation of a dealloc method</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> (ClassA.m)</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> -(void)dealloc {</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> [classB release]; // release the reference that was retained in 'setClassB'</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> }</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">Why did we call this implementation broken? What more could possibly be required from us than that we clean up our own mess? Don't worry, there's no more boring household chores for you to do, but you do have to tell your superclass that it should clean up its mess ;)</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">A perfect implementation of a dealloc method</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> (ClassA.m)</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> -(void)dealloc {</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> [classB release]; // release the reference that was retained in 'setClassB'</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> [super dealloc]; // tell your superclass that it is time to clean up</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> }</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">An alternative implementation of a dealloc method</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> (ClassA.m)</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> -(void)dealloc {</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> [setClassB nil]; // "unset" the reference by setting it to nil</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> [super dealloc]; // tell your superclass that it is time to clean up</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"> }</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">By calling the setter method instead of doing an explicit call to 'release' we do all the memory management of the reference in the setter method. Since memory management is complex it's a good idea to do as little as possible of it.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia"><span class="Apple-style-span" style="font-size: x-large;">Summary</span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">This tutorial is very long, but if you understand all of it you are well equipped to understand the more complicated parts of memory management - in general and in Cocoa. Basic understanding of dynamic memory management and pointers is important for most types of software development, even though some languages claim that you don't have to worry about it. The time spent on understanding it is therefore usually a good investment for the future.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">If there is one thing you should remember from this tutorial it is: free what you allocated, or in Cocoa terms, release what you retained (or allocated).</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">The title of this post indicates that there will be several parts of this tutorial and those will probably deal with stuff like autorelease pools and deep and shallow copying in Cocoa, and probably some words on how to do memory management when you're dealing with objects created from NIB files.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia; min-height: 19.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Georgia">As always, feel free to suggest improvements and point out errors!</p></div></div></div></div>henrikbhttp://www.blogger.com/profile/05536290915355204969noreply@blogger.com6tag:blogger.com,1999:blog-3970047569105596105.post-43888245758939778242009-05-29T10:04:00.006+02:002009-05-29T10:14:59.873+02:00iPhone tutorial: Creating table cells in Interface Builder<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjc0l86rAawQ9kYDtK-2owrUB6tG8l62n63-0C-gb65YUC-5NT3kgd9sP4KeOORVTeVou6i_2GV6r6vBvRSKE2ON-6BnAF3REexTeOMG96vtI6ThPH0mYyXe9MjJ8piF-PB1Vg-9cxSEkg/s1600-h/Bild+1.png"><img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;width: 320px; height: 117px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjc0l86rAawQ9kYDtK-2owrUB6tG8l62n63-0C-gb65YUC-5NT3kgd9sP4KeOORVTeVou6i_2GV6r6vBvRSKE2ON-6BnAF3REexTeOMG96vtI6ThPH0mYyXe9MjJ8piF-PB1Vg-9cxSEkg/s320/Bild+1.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5341156074398882946" /></a><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiERD1RcNoFDHauaRerFf8m5IGm8jJwunlV_8aw2p8ABy2l58MZb6d-cB9YXdD_TedsDbNI8g2XjpHzVd2izWoz3tQiqFctYXXTSFf070BmSRYcKUeL6nv17SAkI61kPWOdLLkHAdhtsaI/s1600-h/Bild+2.png"><img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;width: 320px; height: 117px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiERD1RcNoFDHauaRerFf8m5IGm8jJwunlV_8aw2p8ABy2l58MZb6d-cB9YXdD_TedsDbNI8g2XjpHzVd2izWoz3tQiqFctYXXTSFf070BmSRYcKUeL6nv17SAkI61kPWOdLLkHAdhtsaI/s320/Bild+2.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5341155999096288898" /></a><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjjtoGXhR5bWKPhEQiZHsy2g8UQXp9TfAO5Qjq5dtgBtjPR7AnkqBe5qz9jO4tA1te3f_kI2q683tJT5takmiPOJI_sjPCV_daD6ieNphvUgA6pc1stX-JRfGOnvh4EA-l73VTANRg1S3Q/s1600-h/Bild+3.png"><img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;width: 172px; height: 320px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjjtoGXhR5bWKPhEQiZHsy2g8UQXp9TfAO5Qjq5dtgBtjPR7AnkqBe5qz9jO4tA1te3f_kI2q683tJT5takmiPOJI_sjPCV_daD6ieNphvUgA6pc1stX-JRfGOnvh4EA-l73VTANRg1S3Q/s320/Bild+3.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5341155927896965778" /></a><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgPQ9UzwLH_XedE6-yeQImWQ__9uosRk6tPsEH_4utqO1g7z2kD-tRvlXMKPrd6arjhAxTou08lNuNQlf291CcPIA6IywpiJ0fuKZvOvvv-7NVGkGYIUmNhZRenl4oRU8Vm3eEQ5jK_NOw/s1600-h/Bild+4.png"><img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;width: 320px; height: 254px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgPQ9UzwLH_XedE6-yeQImWQ__9uosRk6tPsEH_4utqO1g7z2kD-tRvlXMKPrd6arjhAxTou08lNuNQlf291CcPIA6IywpiJ0fuKZvOvvv-7NVGkGYIUmNhZRenl4oRU8Vm3eEQ5jK_NOw/s320/Bild+4.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5341155821737439874" /></a><br /><div>Creating complex table view cells programmatically can be quite tedious. In fact, so tedious that it can affect your creativity negatively. Thankfully, it is possible to design table view cells in Interface Builder (IB) and then use them in your application and that is what we're going to explore in this tutorial.</div><div><br /></div><div>While designing table view cells in IB is quite simple and intuitive, using them in an application is far from intuitive. Especially if you want to use a single table view cell for multiple (all?) rows in a table. The problem with using a cell multiple times is that you have to create multiple instances of the UITableViewCell object, which in turn means that you have to load the xib containing the object multiple times. This means that you have to create a reusable IB file (xib/nib).</div><div><br /></div><div>How many instances do you need to create? Well, that is basically decided by the number of visible rows in the table view. Each visible row needs its own instance of a corresponding UITableViewCell object. The cell reuse scheme really won't kick in until you start scrolling the table view, so if you have a table view with 10 visible rows and 20 total rows, you will normally have to create 10 instances of the cell object. This is nothing you should rely on or try to exploit since it's the UITableView object that decides exactly how many instances you need of a specific cell. It does this by returning nil when you call 'dequeueReusableCellWIthIdentifier', which basically is an order to create a new instance - simple as that.</div><div><br /></div><div>This tutorial could be seen as "part 4" of the "UITableView from the ground up", but I decided against it since it is more or less independent - focusing just on how to load and reuse UITableViewCell objects from an IB file. Therefore, we're going to create a new project instead of modifying the one we created in part 1.</div><div><br /></div><div><span class="Apple-style-span" style="font-size:x-large;">Create the project</span></div><div><br /></div><div>Start XCode and choose "File/New Project" from the menu to create a "Window-Based Application" and name it "TableCellLoader". We're going to create a few classes right away, so select the Classes-group in the "Groups & Files" panel i XCode. Then choose "File/New File" from the menu and create a NSObject subclass called "CellOwner.m" (remember to check the "Also create h-file" checkbox). After that, choose "File/New File" again and create a UITableViewCell subclass called "Cell1" and again to create yet another UITableViewCell subclass, this time called "Cell2".</div><div><br /></div><div><span class="Apple-style-span" style="font-size:x-large;">Classes/Cell1.h</span></div><div><br /></div><div>The UITableViewCell we're going to create in IB will be represented by an UITableViewCell subclass in our application. We're actually going to create two similar cells in IB - "Cell1" and "Cell2" - which will only differ in regards to the layout of their contents. The cell content is very simple - two UILabels which will allow us to display two strings. Since the cells are so similiar Cell2.h will be identical toll Cell1.h - with the exception of the name of the class.</div><div><br /></div><div>In order to access the two labels in the table view cell, we need to define two instance variables - "label" and "label2" - containing pointers to UILabel objects. Since we'll be manipulating them from IB, we also need to make them into properties and mark them with IBOutlet. Those changes should result in the following:</div><div><br /></div><div>@interface Cell1 : UITableViewCell {</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>UILabel *label;</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>UILabel *label2;</div><div>}</div><div><br /></div><div>@property (nonatomic, retain) IBOutlet UILabel *label;</div><div>@property (nonatomic, retain) IBOutlet UILabel *label2;</div><div><br /></div><div><span class="Apple-style-span" style="font-size:x-large;">Classes/Cell1.m</span></div><div><br /></div><div>Since we created this file as a subclass of UITableViewCell it will already contain some code, but ignore that for now since all we need to do right now is to synthesize the properties we created in the h-file. Since As we mentioned above "Cell1" and "Cell2" are very similar so apply the same changes to the Cell2.m.</div><div><br /></div><div>All we need to do is to add two @synthesize statements right after the @implementation statement:</div><div><br /></div><div>@implementation Cell1</div><div>@synthesize label;</div><div>@synthesize label2;</div><div><br /></div><div><span class="Apple-style-span" style="font-size:x-large;">Classes/CellOwner.h</span></div><div><br /></div><div>The CellOwner class will be used to load UITableViewCell objects from IB (xib/nib) files and the rather strange name was chosen because the CellOwner class will be set as the "File's owner" in the IB files for the UITableViewCell objects ("Cell1" and "Cell2").</div><div><br /></div><div>Since this class is just some kind of "support" class for the IB object loading procedure it doesn't contain much or do much. All it contains is a pointer to the UITableViewCell subclass that is loaded from the IB file and a method which loads an IB file. Therefore the h-file will be quite simple:</div><div><br /></div><div>@interface CellOwner : NSObject {</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>UITableViewCell *cell;</div><div>}</div><div><br /></div><div>@property (nonatomic, retain) IBOutlet UITableViewCell *cell;</div><div><br /></div><div>- (BOOL)loadMyNibFile:(NSString *)nibName;</div><div><br /></div><div><span class="Apple-style-span" style="font-size:x-large;">Classes/CellOwner.m</span></div><div><br /></div><div>Since we defined a property in the h-file, we - as always - need to add a corresponding @synthesize statement in the m-file, so add the following right after the @implementation statement:</div><div><br /></div><div>@synthesize cell;</div><div><br /></div><div>In the h-file we also declared a method which we need to implement in the m-file, so add the following:</div><div><br /></div><div>- (BOOL)loadMyNibFile:(NSString *)nibName {</div><div> // The myNib file must be in the bundle that defines self's class.</div><div> if ([[NSBundle mainBundle] loadNibNamed:nibName owner:self options:nil] == nil)</div><div> {</div><div> NSLog(@"Warning! Could not load %@ file.\n", nibName);</div><div> return NO;</div><div> }</div><div> return YES;</div><div>}</div><div><br /></div><div>The source code for this method was taking more or less directly from an example in the "Resource Programming Guide" which you can find by searching for "loadMyNibFile" in the API docs in XCode (remember to select "Full-Text" in the upper left corner of the API docs window.</div><div><br /></div><div>As you can see, it's quite simple to load an IB file - it's basically just one line of code! The rest of the code is error handling. To keep up the pace of this tutorial we won't dive into the details of the NSBundle class, so if you want to know more about that right now, please search for it in the API docs.</div><div><br /></div><div>'loadNibNamed' takes three arguments; the name of the nib (IB) file to load, the owner of the file ("File's owner" in IB) and something called 'options'. As we said above, our single CellOwner object will be the "File's owner" of all the cells we load, which explains why we pass 'self' as the value of the 'owner' argument. The 'options' argument is only used if the IB file we load contain any non-standard "proxy objects". We don't use this feature and thus we can pass 'nil' as the value.</div><div><br /></div><div><span class="Apple-style-span" style="font-size:x-large;">Classes/TableCellLoaderAppDelegate.h</span></div><div><br /></div><div>Our application delegate will contain a reference to an object of the CellOwner class we created above, so add an instance variable and a property for it as well as marking it as IBOutlet since we'll create it in IB. After you're done, the file should look like this:</div><div><br /></div><div>@interface TableCellLoaderAppDelegate : NSObject <uiapplicationdelegate> {</uiapplicationdelegate></div><div> UIWindow *window;</div><div> CellOwner *cellOwner;</div><div>}</div><div><br /></div><div>@property (nonatomic, retain) IBOutlet UIWindow *window;</div><div>@property (nonatomic, retain) IBOutlet CellOwner *cellOwner;</div><div><br /></div><div><span class="Apple-style-span" style="font-size:x-large;">Classes/TableCellLoaderAppDelegate.m</span></div><div><br /></div><div>This is the file we'll keep adding code to during the tutorial but right now we'll just add the @synthesize statement corresponding to the property we added in the h-file ("cellOwner"). So add the following just below the already existing "@synthesize window" statement:</div><div><br /></div><div>@synthesize cellOwner;</div><div><br /></div><div><span class="Apple-style-span" style="font-size:x-large;">Test build</span></div><div><br /></div><div>Build the project in XCode (CMD-B) to verify that everything works. There should be no warnings or errors reported.</div><div><br /></div><div><span class="Apple-style-span" style="font-size:x-large;">Resources/MainWindow.xib</span></div><div><br /></div><div>Double-click on Resources/MainWindow.xib to start IB and load the file. As always, remember to switch to "hierarchical view mode" by pressing the middle button above the "View Mode" text in the upper left corner of the MainWindow.xib window.</div><div><br /></div><div>We need a table view in order to be able to experiment with table view cells, so let's add one to our window. Open the Library window in IB (CMD-L) and drag a "Table View" object from the "Data Views" section and drop it onto the Window object in the MainWindow.xib window.</div><div><br /></div><div>A table view needs the help of two other objects to function properly - a 'delegate' and a 'dataSource' - so we'll need to connect those outlets of the the "Table View" object we just added. CTRL-drag from "Table View" to "Table Cell Loader App Delegate" and choose 'delegate' from the window that pops up. Repeat the process for the 'dataSource'.</div><div><br /></div><div>We're going to use a single instance of our "CellOwner" object to load our UITableViewCell objects from IB files, so let's create that one as well. Drag a "Object" object from the "Controllers" section of the Library window (CMD-L) and drop it at the end of the list in the MainWindow.xib window. </div><div><br /></div><div>Select the "Object" object in MainWindow.xib and press CMD-4 to bring the Inspector window to the front and select the Identity tab. Here you should change the class of the object to "CellOwner" in the drop-down list. Remember that we added a "cellOwner" property to our application delegate? Now is the time to connect it, so CTRL-drag from "Table Cell Loader App Delegate" to "Cell Owner" in MainWindow.xib and choose "cellOwner" in the pop-up window that appears.</div><div><br /></div><div>We're done with MainWindow.xib so save the file by pressing CMD-S.</div><div><br /></div><div><span class="Apple-style-span" style="font-size:x-large;">Resources/Cell1.xib</span></div><div><br /></div><div>Now it's time to create our custom UITableViewCell objects in IB, so choose "File/New" in the menu and select the "Empty" template. Select the new window that appears ("Untitled") and choose "File/Save As" from the menu. Ensure that you're in the "TableCellLoader" directory and then save the file as "Cell1". IB will ask you if you want to add the file to the project, which we do so check the checkbox and press "Add".</div><div><br /></div><div>This file should contain the first of our UITableViewCell object, so drag a "Table View Cell" from the "Data Views" section of the Library window (CMD-L) into the Cell1 window. The cells we're creating are UITableViewCell subclasses, so the first thing we need to do is set the class of the "Table View Cell" object by selecting it, pressing CMD-4 and select "Cell1" from the drop-down menu.</div><div><br /></div><div>As we mentioned earlier our table view cells should contain two UILabel objects accessible through the 'label' and 'label2' properties/outlets of our Cell1 class, so let's create them. Since we want to layout the UILabel objects in a specific way we should bring up the "design window" of the "Cell1" object by double-clicking on it.</div><div><br /></div><div>When doing this, a small table view cell shaped window should appear. Notice that the cell by default has a blue "disclosure button" to the right? We're going to use it in our tutorial so we'll keep it, but it's no problem deleting it if you want to.</div><div><br /></div><div>Drag a "Label" object from the "Inputs & Values" section of the Library window (CMD-L) into the design window of the Cell1 object and place it to the far left in the dashed rectangle. Drag another "Label" object from the Library window and place it to the far right in the dashed rectangle (see screenshot).</div><div><br /></div><div>In order to be able to access these labels from our applications we need to connect them to the 'label' and 'label2' outlets we created in the Cell1.h file. To do this, CTRL-drag from the "Cell1" object in the Cell1 window to the leftmost label object in the "Cell1" design window and select the 'label' outlet in the window that pops up. Repeat the process to connect the rightmost label to the 'label2' outlet.</div><div><br /></div><div>As we have mentioned in earlier tutorials, the table view tries to reuse its cells as a way to optimise its performance. The rationale behind this is that if object creation is kept to a minimun the performance will increase. In order for this reuse scheme to work, each "type" of cell in the table needs to be assigned a "reuse identifier". If there are two types of cells in a table there are only two different identifiers, even if the total amount of cells (rows) is much larger. The UITableViewCell property which specifies the "reuse identifier" is called 'reuseIdentifier' but here in IB it's just called "Identifier", which you can see if you select the "Cell1" object and press CMD-1. Enter "Cell1" in the Identifier field.</div><div><br /></div><div>Now it's time to configure the "File's Owner" object. Start by changing the class to "CellOwner" since we previosly explained that "CellOwner" will be the owner of all our IB created cells. Do this by selecting "File's Onwer", press CMD-4 and choose "CellOwner" from the drop-down menu.</div><div><br /></div><div>Once the class is set to "CellOwner" we can connect the 'cell' outlet of the "File's Owner" object to the "Cell1" object. Do this by CTRL-dragging from "File's owner" to "Cell1" and choose the 'cell' outlet from the window that pops up.</div><div><br /></div><div>We're done with this file now, so save it by pressing CMD-S.</div><div><br /></div><div><span class="Apple-style-span" style="font-size:x-large;">Resources/Cell2.xib</span></div><div><br /></div><div>Cell2.xib is almost identical to Cell1.xib so repeat all the steps from above but lay out the UILabels a bit differently so it's possible to discern between the two cells. I chose to place the first label slightly to the left of the center of the dashed area and the second label slightly to the right instead of to the left and right extremes (see screenshot).</div><div><br /></div><div>When you're done with all the connections, class changes, etc. remember to save the file by pressing CMD-S. </div><div><br /></div><div>A nice way of seeing if all the files in IB are saved is to activate the "Window" in IB and look at the list of window names at the end of the menu. Unsaved windows have a small dot the left of the name, so if you've save all windows you should see no dots.</div><div><br /></div><div><span class="Apple-style-span" style="font-size:x-large;">Classes/TableCellLoaderAppDelegate.m</span></div><div><br /></div><div>Now it's time to return to XCode to implement the required methods of the UITableViewDelegate and UITableViewDataSource protocols since we connected the 'delegate' and 'dataSource' outlets of our "Table View" object in IB to the "Table Cell Loader App Delegate" object which is implemented in TableCellLoaderAppDelegate.m.</div><div><br /></div><div>We're going to work with the Cell1 and Cell2 classes so start by importing the corresponding h-files by adding the following right after the already existing #import statement:</div><div><br /></div><div>#import "Cell1.h"</div><div>#import "Cell2.h"</div><div><br /></div><div>After that we should configure the number of rows in our table by adding the following just below the already present 'dealloc' method:</div><div><br /></div><div>- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>return 20;</div><div><span class="Apple-tab-span" style="white-space:pre"> </span></div><div>}</div><div><br /></div><div>Finally, we're coming to the really interesting part of this tutorial - how to provide our custom made, IB designed UITableViewCell objects to the table view. We do this adding a quite impressive 'cellForRowAtIndexPath' method just below the 'numberOfRowsInSection' method.</div><div><br /></div><div>- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {</div><div><span class="Apple-tab-span" style="white-space:pre"> </span></div><div><span class="Apple-tab-span" style="white-space:pre"> </span>// init the return value to nil</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>UITableViewCell *cell = nil;</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>if ( (indexPath.row & 1) == 0 ) {</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>// "even rows", that is, row 0, row 2, row 4, etc.</div><div><span class="Apple-tab-span" style="white-space:pre"> </span></div><div><span class="Apple-tab-span" style="white-space:pre"> </span>// check if the table view has a cell of the appropriate type we can reuse</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>Cell1 *cell1 = (Cell1 *)[tableView dequeueReusableCellWithIdentifier:@"Cell1"];</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>if ( cell1 != nil ) {</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>// yes it had a cell we could reuse</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>NSLog(@"reusing cell '%@' (%p) for row %d...", cell1.reuseIdentifier, cell1, indexPath.row);</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>} else {</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>// no cell to reuse, we have to create a new instance by loading it from the IB file</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>NSString *nibName = @"Cell1";</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>[cellOwner loadMyNibFile:nibName];</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>// get a pointer to the loaded cell from the cellOwner and cast it to the appropriate type</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>cell1 = (Cell1 *)cellOwner.cell;</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>NSLog(@"Loading cell from nib %@", nibName);</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>}</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>// set the labels to the appropriate text for this row</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>cell1.label.text = [NSString stringWithFormat:@"this is..."];</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>cell1.label2.text = [NSString stringWithFormat:@"...row %d", indexPath.row];</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>cell = cell1;</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>} else {</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>// "odd rows", that is, row 1, row 3, row 5, etc.</div><div><span class="Apple-tab-span" style="white-space:pre"> </span></div><div><span class="Apple-tab-span" style="white-space:pre"> </span>// check if the table view has a cell of the appropriate type we can reuse</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>Cell2 *cell2 = (Cell2 *)[tableView dequeueReusableCellWithIdentifier:@"Cell2"];</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>if ( cell2 != nil ) {</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>// yes it had a cell we could reuse</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>NSLog(@"reusing cell '%@' (%p) for row %d...", cell2.reuseIdentifier, cell2, indexPath.row);</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>} else {</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>// no cell to reuse, we have to create a new instance by loading it from the IB file</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>NSString *nibName = @"Cell2";</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>[cellOwner loadMyNibFile:nibName];</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>// get a pointer to the loaded cell from the cellOwner and cast it to the appropriate type</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>cell2 = (Cell2 *)cellOwner.cell;</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>NSLog(@"Loading cell from nib %@", nibName);</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>}</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>// set the labels to the appropriate text for this row</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>cell2.label.text = [NSString stringWithFormat:@"this is..."];</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>cell2.label2.text = [NSString stringWithFormat:@"...row %d", indexPath.row];</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>cell = cell2;</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>}</div><div><br /></div><div><span class="Apple-tab-span" style="white-space:pre"> </span>// return the cell which will be either a "Cell1" or "Cell2" object.</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>return cell;</div><div>}</div><div><br /></div><div>I have tried to explain what's going on in the inlined comments so I won't bore you with repeating all that here in the text. Instead I think we're more than ready to see some results - yeah, it's time for a test run!</div><div><br /></div><div><span class="Apple-style-span" style="font-size:x-large;">Test run</span></div><div><br /></div><div>Build and run the project in XCode by pressing CMD-Return and once the simulator have started you should see a table where the "even rows" have one appearance and the "odd rows" another. Try to scroll down until you get to row 19 where the table ends. If you switch back to XCode and bring the console window to the front (CMD-R) you should see something like this.</div><div><br /></div><div>2009-05-29 09:56:37.479 TableCellLoader[14364:20b] Loading cell from nib Cell1 for row 10...</div><div>2009-05-29 09:56:37.488 TableCellLoader[14364:20b] Loading cell from nib Cell2 for row 9...</div><div>2009-05-29 09:56:37.491 TableCellLoader[14364:20b] Loading cell from nib Cell1 for row 8...</div><div>2009-05-29 09:56:37.493 TableCellLoader[14364:20b] Loading cell from nib Cell2 for row 7...</div><div>2009-05-29 09:56:37.497 TableCellLoader[14364:20b] Loading cell from nib Cell1 for row 6...</div><div>2009-05-29 09:56:37.501 TableCellLoader[14364:20b] Loading cell from nib Cell2 for row 5...</div><div>2009-05-29 09:56:37.508 TableCellLoader[14364:20b] Loading cell from nib Cell1 for row 4...</div><div>2009-05-29 09:56:37.513 TableCellLoader[14364:20b] Loading cell from nib Cell2 for row 3...</div><div>2009-05-29 09:56:37.522 TableCellLoader[14364:20b] Loading cell from nib Cell1 for row 2...</div><div>2009-05-29 09:56:37.525 TableCellLoader[14364:20b] Loading cell from nib Cell2 for row 1...</div><div>2009-05-29 09:56:37.527 TableCellLoader[14364:20b] Loading cell from nib Cell1 for row 0...</div><div>2009-05-29 09:56:39.434 TableCellLoader[14364:20b] Loading cell from nib Cell2 for row 11...</div><div>2009-05-29 09:56:39.514 TableCellLoader[14364:20b] reusing cell 'Cell1' (0x52d780) for row 12...</div><div>2009-05-29 09:56:39.610 TableCellLoader[14364:20b] reusing cell 'Cell2' (0x52d200) for row 13...</div><div>2009-05-29 09:56:40.001 TableCellLoader[14364:20b] reusing cell 'Cell1' (0x52c870) for row 14...</div><div>2009-05-29 09:56:40.082 TableCellLoader[14364:20b] reusing cell 'Cell2' (0x52c360) for row 15...</div><div>2009-05-29 09:56:40.154 TableCellLoader[14364:20b] reusing cell 'Cell1' (0x52c0e0) for row 16...</div><div>2009-05-29 09:56:40.482 TableCellLoader[14364:20b] reusing cell 'Cell2' (0x52bb60) for row 17...</div><div>2009-05-29 09:56:40.543 TableCellLoader[14364:20b] reusing cell 'Cell1' (0x52b610) for row 18...</div><div>2009-05-29 09:56:40.576 TableCellLoader[14364:20b] Loading cell from nib Cell2 for row 19...</div><div>2009-05-29 09:56:42.298 TableCellLoader[14364:20b] reusing cell 'Cell1' (0x528fd0) for row 10...</div><div>2009-05-29 09:56:42.448 TableCellLoader[14364:20b] reusing cell 'Cell2' (0x526280) for row 9...</div><div><br /></div><div>As we have seen in earlier tutorials, no reusal of cell is going on for the first rows since they all are visible and every visible row needs its own UITableViewCell instance. Once we start scrolling, the reuse scheme kicks into effect though. It's not just the first rows that have their own instances though. As you can see row 19 was also loaded/created from the IB file (nib).</div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;">Adding interaction</span></div><div><br /></div><div>If you want to interact with the cells (detecting row selection or "disclosure button" touches) you can add the following to TableCellLoaderAppDelegate.m:</div><div><br /></div><div>- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath {</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>NSLog(@"accessoryButtonTappedForRowWithIndexPath: row=%d", indexPath.row);</div><div>}</div><div><br /></div><div>- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>NSLog(@"didSelectRowAtIndexPath: row=%d", indexPath.row);</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>[tableView deselectRowAtIndexPath:indexPath animated:YES];</div><div>}</div><div><br /></div><div>This won't do anything else but log some messages in the XCode console window, but it opens up a world of possibilities. It also demonstrates that the "disclosure button" added by default by IB works right out of the box.</div><div><br /></div><div><span class="Apple-style-span" style="font-size:x-large;">Summary</span></div><div><br /></div><div><div>There are many ways of using Interface Builder created table cells in your application but the procedure I have presented here in this tutorial is quite simple to understand, at least for a novice iPhone developer like myself . Remember that things I write about in this blogs are things that I have recently began understanding myself! That is, I am no expert and don't claim to present the best, or even correct, way of doing things. What I'm trying to say is that I welcome all kinds of comments! ;)</div><div><br /></div></div>henrikbhttp://www.blogger.com/profile/05536290915355204969noreply@blogger.com13tag:blogger.com,1999:blog-3970047569105596105.post-34060886098427380072009-05-26T16:37:00.002+02:002009-05-29T07:01:16.604+02:00iPhone tutorial: UITableView from the ground up, part 3Now that we understand the really basic stuff about the UITableView and UITableViewCell classes, we're ready to move on to some simple interaction. We'll continue modifying the Table1 project from <a href="http://humblecoder.blogspot.com/2009/05/iphone-tutorial-uitableview-from-ground.html">part 1</a> and <a href="http://humblecoder.blogspot.com/2009/05/iphone-tutorial-uitableview-from-ground.html">part 2</a> so please check those parts out before reading any further.<div><br /></div><div><span class="Apple-style-span" style="font-size: x-large;">Classes/Table1AppDelegate.m</span></div><div><br /></div><div><div>Modify the 'cellForRowAtIndexPath' method to make it look like this:</div><div><br /></div><div><div>- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {</div><div><br /></div><div><span class="Apple-tab-span" style="white-space: pre; "> </span>// try to retrieve "cell 1" from the UITableView cache</div><div><span class="Apple-tab-span" style="white-space: pre; "> </span>UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell 1"];</div><div><span class="Apple-tab-span" style="white-space: pre; "> </span>if ( cell == nil ) {</div><div><span class="Apple-tab-span" style="white-space: pre; "> </span>// "cell 1" wasn't present in the cache, so create it</div><div><span class="Apple-tab-span" style="white-space: pre; "> </span>cell = [[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:@"cell 1"];</div><div><span class="Apple-tab-span" style="white-space: pre; "> </span>cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;</div><div><span class="Apple-tab-span" style="white-space: pre; "> </span>NSLog(@"creating cell '%@' (%p) for row %d...", cell.reuseIdentifier, cell, indexPath.row);</div><div><span class="Apple-tab-span" style="white-space: pre; "> </span>} else {</div><div><span class="Apple-tab-span" style="white-space: pre; "> </span>// "cell 1" was present in the cache, so log that we're reusing it</div><div><span class="Apple-tab-span" style="white-space: pre; "> </span>NSLog(@"reusing cell '%@' (%p) for row %d...", cell.reuseIdentifier, cell, indexPath.row);</div><div><span class="Apple-tab-span" style="white-space: pre; "> </span>}</div><div><span class="Apple-tab-span" style="white-space: pre; "> </span> </div><div><span class="Apple-tab-span" style="white-space: pre; "> </span>cell.text = [NSString stringWithFormat:@"this is row %d", indexPath.row];</div><div><span class="Apple-tab-span" style="white-space: pre; "> </span></div><div><span class="Apple-tab-span" style="white-space: pre; "> </span>return cell;</div><div>}</div><div><br /></div><div>The only new thing here really is the </div><div><br /></div><div><div><span class="Apple-tab-span" style="white-space: pre; "> </span>cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;</div><div><br /></div><div>row which says that we want a standard "detail disclosure" button to the right of the text in our cell. This is the standard way in the iPhone user interface to indicate that touching this cell will take you to a new screen. Just by adding this you also instruct the table view to start sending 'accessoryButtonTappedForRowWithIndexPath' to its 'delegate' object.</div><div><br /></div></div></div></div><div>In order to receive those messages we have to implement the appropriate method, so add the following to the end of the file.</div><div><br /></div><div><div>- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath {</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>NSLog(@"accessoryButtonTappedForRowWithIndexPath: row=%d", indexPath.row);</div><div>}</div><div><br /></div><div><span class="Apple-style-span" style="font-size: large;">A short note on the use of indexPath in combination with table views</span></div><div><br /></div><div>You might have noticed that a lot of the delegate methods for the table view have an 'indexPath' argument of type NSIndexPath. I wrote quite a lot about index paths <a href="http://humblecoder.blogspot.com/2009/04/iphone-tutorial-navigation-based_24.html">in a previous post </a>so have a look there if you want a full explanation. Here I will just mention that the index paths used in conjunction with table views have two levels which represent the section and the row indices of the table. As an alternative solution two arguments 'section' and 'row' could have been used instead of the 'indexPath' argument. If the table only has one section all you need to care about is the row index, which you read from the indexPath.row property. <br /></div><div><br /></div><div><span class="Apple-style-span" style="font-size: large;">Test run</span></div><div><br /></div><div>Build and run the project in XCode (CMD-Return), wait for the simulator to start and then go back to XCode to bring the XCode console window to the front (CMD-R). Touch a few disclosure buttons and you should see something like this in the console window:</div><div><br /></div><div><div>009-05-26 17:07:24.807 Table1-2[11521:20b] accessoryButtonTappedForRowWithIndexPath: row=0</div><div>2009-05-26 17:07:27.655 Table1-2[11521:20b] accessoryButtonTappedForRowWithIndexPath: row=1</div><div>2009-05-26 17:07:29.471 Table1-2[11521:20b] accessoryButtonTappedForRowWithIndexPath: row=2</div><div>2009-05-26 17:07:30.943 Table1-2[11521:20b] accessoryButtonTappedForRowWithIndexPath: row=3</div><div>2009-05-26 17:07:35.919 Table1-2[11521:20b] accessoryButtonTappedForRowWithIndexPath: row=8</div><div><br /></div><div>What you do with these messages is completely up to you, but a very common way of using this information is to switch to another table view which shows information "on the next level" in some kind of hierarchical data. You often see this when table views are used together with navigation controllers since they offer a very easy way of switching to a new view as well as providing a way to get back to the last one (the "back button").</div><div><br /></div><div><span class="Apple-style-span" style="font-size: x-large;">Classes/Table1AppDelegate.m</span><br /></div></div><div><div><br /></div><div>Another way of adding interaction to a table is by detecting "selections", that is detecting that the user touched a row/cell in the table. Above we detected that the user touched the "disclosure button", but here we'll detect touches to anywhere outside of the disclosure button. Add the following to the end of the file.</div><div><br /></div></div><div>- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {<br /></div><div><div><span class="Apple-tab-span" style="white-space: pre; "> </span>NSLog(@"didSelectRowAtIndexPath: row=%d", indexPath.row);</div><div>}</div><div><br /></div><div><span class="Apple-style-span" style="font-size: large;">Test run</span></div><div><br /></div><div>Build and run the project and touch a few rows once the simulator has started. You should see something like this in the XCode console window:</div><div><br /></div><div><div>2009-05-26 17:12:00.289 Table1-2[11530:20b] didSelectRowAtIndexPath: row=1</div><div>2009-05-26 17:12:02.770 Table1-2[11530:20b] didSelectRowAtIndexPath: row=2</div><div>2009-05-26 17:12:03.882 Table1-2[11530:20b] didSelectRowAtIndexPath: row=5</div><div>2009-05-26 17:12:05.322 Table1-2[11530:20b] didSelectRowAtIndexPath: row=8</div><div><br /></div><div>If you are really observant you probably noticed that the row you touched stays selected (as indicated by the blue colour). This is because it is our responsibility to deselect it, which is accomplished by calling 'deselectRowAtIndexPath':</div><div><br /></div><div><div>- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>NSLog(@"didSelectRowAtIndexPath: row=%d", indexPath.row);</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>[tableView deselectRowAtIndexPath:indexPath animated:YES];</div><div>}</div><div><br /></div><div><span class="Apple-style-span" style="font-size: x-large;">Summary</span></div><div><br /></div><div>Allowing the user to interact with the table view opens up for a lot of possibilities and it is nice to know that the detection of user interactions is as easy as this. At least for simple interactions.</div><div><br /></div></div></div></div></div>henrikbhttp://www.blogger.com/profile/05536290915355204969noreply@blogger.com1tag:blogger.com,1999:blog-3970047569105596105.post-44846193694394099122009-05-24T10:24:00.006+02:002009-05-29T07:01:16.604+02:00iPhone tutorial: UITableView from the ground up, part 2In <a href="http://www.blogger.com/blecoder.blogspot.com/2009/05/iphone-tutorial-uitableview-from-ground.html">part 1 of this tutorial</a> we created a simple table view with just a few lines of code to show that, even though table views are a bit intimidating, they are possible to fully understand if you take small enough steps. This part of the tutorial is going to continue from where the last part ended, so if you haven't already created the "Table1" XCode project you will have to work through part 1 first.<div><br /></div><div><span class="Apple-style-span" style="font-size: x-large;">Classes/Table1AppDelegate.m</span></div><div><br /></div><div>We're going to start by exploring how to reuse cells, since this is something Apple more or less recommends us to do. The first hint Apple gives us is that there is only one way to initialise a UITableViewCell and that involves calling 'initWithFrame:reuseIdentifier' which takes a "reuse identifier" as the second argument. You can set this to nil - as we did in part 1 - if you really don't want to reuse the cell, but that will probably affect the performance of (large) table views significally.</div><div><br /></div><div>The table view calls it's 'dataSource' delegate whenever it needs a cell for a specific row since it has no clue of how a specific cell (row) should look like (background colour, detail disclosure buttons, etc.) or what it should contain (text, images, etc.). However, if you assign a "reuse identifier" (name) to the cell you provide to the table view, the table view will try to cache the cell for you. This means that the table view tries to store the cells created by the "data source" for later use so that the data source doesn' t have to create a new cell everytime - sometimes it will be able to reuse an existing cell.</div><div><br /></div><div>The UITableView class has a method called 'dequeueReuseableCellWithIdentifier' which is used to retrieve a UITableViewCell object from the UITableView cache, if present. If the cell isn't present, it returns nil. Let's modify our 'tableView:cellForRowAtIndexPath' method to make use of this reuse-scheme. We'll call our cell "cell1" in the example below:</div><div><br /></div><div><div><div>- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {</div><div><br /></div><div><span class="Apple-tab-span" style="white-space:pre"> </span>// try to retrieve "cell1" from the UITableView cache</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell1"];</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>if ( cell == nil ) {</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>// "cell 1" wasn't present in the cache, so create it</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>NSLog(@"creating cell for row %d...", indexPath.row);</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>cell = [[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:@"cell1"];</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>} else {</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>// "cell 1" was present in the cache, so log that we're reusing it</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>NSLog(@"reusing cell for row %d...", indexPath.row);</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>}</div><div><br /></div><div><span class="Apple-tab-span" style="white-space:pre"> </span>if ( indexPath.row == 0 ) { </div><div><span class="Apple-tab-span" style="white-space:pre"> </span>// assign a text to row 0</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>cell.text = [NSString stringWithFormat:@"this is row %d", indexPath.row];</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>}</div><div><span class="Apple-tab-span" style="white-space:pre"> </span></div><div><span class="Apple-tab-span" style="white-space:pre"> </span>return cell;</div><div>}</div><div><br /></div></div><div>The comments in the source code above tries to explain what we're doing, so I won't elaborate on that. One thing is worth mentioning though and that is the 'indexPath.row' statement. If you look in the API docs for NSIndexPath you won't see any 'row' property. This is because "UITableView declares a category on NSIndexPath that enables you to get the represented row index (row property)", as can be read in the API docs for the UITableViewDelegate protocol. The "category" that is mentioned above is an Obejctive C feature which allows you to add methods to a class without actually subclassing it.</div><div><br /></div><div><span class="Apple-style-span" style="font-size: x-large;">Exploring the reuse scheme</span></div><div><br /></div><div>If you compile and run (CMD-Return) in XCode and wait for the simulator to start, you'll see a table with the text "this is row 0" in the first row. Apart from that it is empty, but you are still able to select rows 1 and 2 as well. If you check the console window (CMD-R) in XCode, you'll see somthing like this:</div><div><br /></div><div><div>2009-05-24 18:29:06.375 Table1-2[8618:20b] creating cell for row 2...</div><div>2009-05-24 18:29:06.387 Table1-2[8618:20b] creating cell for row 1...</div><div>2009-05-24 18:29:06.394 Table1-2[8618:20b] creating cell for row 0...</div></div><div><br /></div><div>The first thing to notice is that UITableView seems to have requested cells in the "wrong order", that is starting with row 2 instead of row 0. This is of course nothing you should take advantage of, but it's interesting to note. The really interesting thing to note, though, is that none of the cells seems to have been reused, even though we initialised all our cells with the same reuse identfier "cell1". What's going on, is the reuse mechanism broken or what?</div><div><br /></div><div><span class="Apple-style-span" style="font-size: large;">Adding more rows</span></div><div><br /></div><div>Let's see what happens if we change the number of rows in our table by editing 'tableView:numberOfRowsInSection' to make it return 10 instead of 3.</div><div><br /></div><div><div>- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>return 10;</div><div>}</div><div><br /></div></div><div>Build and run (CMD-Return), wait for the simulator to start and then bring the XCode console window to the front (CMD-R). Now scroll the table view by "moving your finger upwards" on the simulator and you should see something like this in the console window:</div><div><br /></div><div><div>2009-05-24 18:44:49.417 Table1-2[8663:20b] creating cell for row 9...</div><div>2009-05-24 18:44:49.425 Table1-2[8663:20b] creating cell for row 8...</div><div>2009-05-24 18:44:49.426 Table1-2[8663:20b] creating cell for row 7...</div><div>2009-05-24 18:44:49.427 Table1-2[8663:20b] creating cell for row 6...</div><div>2009-05-24 18:44:49.431 Table1-2[8663:20b] creating cell for row 5...</div><div>2009-05-24 18:44:49.434 Table1-2[8663:20b] creating cell for row 4...</div><div>2009-05-24 18:44:49.435 Table1-2[8663:20b] creating cell for row 3...</div><div>2009-05-24 18:44:49.435 Table1-2[8663:20b] creating cell for row 2...</div><div>2009-05-24 18:44:49.436 Table1-2[8663:20b] creating cell for row 1...</div><div>2009-05-24 18:44:49.436 Table1-2[8663:20b] creating cell for row 0...</div><div>2009-05-24 18:45:25.892 Table1-2[8663:20b] reusing cell for row 1...</div><div>2009-05-24 18:45:25.975 Table1-2[8663:20b] reusing cell for row 0...</div><div>2009-05-24 18:45:39.922 Table1-2[8663:20b] reusing cell for row 0...</div><div>2009-05-24 18:45:41.884 Table1-2[8663:20b] reusing cell for row 0...</div><div>2009-05-24 18:45:42.053 Table1-2[8663:20b] reusing cell for row 1...</div><div>2009-05-24 18:45:42.187 Table1-2[8663:20b] reusing cell for row 0...</div><div>2009-05-24 18:45:43.552 Table1-2[8663:20b] reusing cell for row 1...</div><div>2009-05-24 18:45:43.566 Table1-2[8663:20b] reusing cell for row 0...</div><div><br /></div><div>As you can see, the 10 first rows are created without reusing any cells, but as soon as you start scrolling the reuse mechanism seems to kick in. Everytime row 0, 1 and 2 "reappears" the cells seem to be reused.</div><div><br /></div><div><span class="Apple-style-span" style="font-size: large;">Adding even more rows</span></div><div><br /></div><div><div>Wonder what happens if 'tableView:numberOfRowsInSection' returns 100 instead of 10? Edit the method, build and run (CMD-Return), wait for the simulator to start, bring the XCode console window to the front (CMD-R), start scrolling around and you should see somthing like this:</div><div><br /></div></div><div><div>2009-05-24 18:54:22.903 Table1-2[8704:20b] creating cell for row 10...</div><div>2009-05-24 18:54:22.926 Table1-2[8704:20b] creating cell for row 9...</div><div>2009-05-24 18:54:22.932 Table1-2[8704:20b] creating cell for row 8...</div><div>2009-05-24 18:54:22.939 Table1-2[8704:20b] creating cell for row 7...</div><div>2009-05-24 18:54:22.947 Table1-2[8704:20b] creating cell for row 6...</div><div>2009-05-24 18:54:22.951 Table1-2[8704:20b] creating cell for row 5...</div><div>2009-05-24 18:54:22.953 Table1-2[8704:20b] creating cell for row 4...</div><div>2009-05-24 18:54:22.955 Table1-2[8704:20b] creating cell for row 3...</div><div>2009-05-24 18:54:22.962 Table1-2[8704:20b] creating cell for row 2...</div><div>2009-05-24 18:54:22.964 Table1-2[8704:20b] creating cell for row 1...</div><div>2009-05-24 18:54:22.968 Table1-2[8704:20b] creating cell for row 0...</div><div>2009-05-24 18:54:38.025 Table1-2[8704:20b] reusing cell for row 10...</div><div>2009-05-24 18:54:40.375 Table1-2[8704:20b] creating cell for row 11...</div><div>2009-05-24 18:54:40.526 Table1-2[8704:20b] reusing cell for row 12...</div><div>2009-05-24 18:54:41.365 Table1-2[8704:20b] reusing cell for row 13...</div><div>2009-05-24 18:54:42.043 Table1-2[8704:20b] reusing cell for row 14...</div><div>2009-05-24 18:54:42.136 Table1-2[8704:20b] reusing cell for row 15...</div><div>2009-05-24 18:54:42.693 Table1-2[8704:20b] reusing cell for row 16...</div><div>2009-05-24 18:54:42.857 Table1-2[8704:20b] reusing cell for row 17...</div><div>2009-05-24 18:54:43.040 Table1-2[8704:20b] reusing cell for row 18...</div><div>2009-05-24 18:54:43.374 Table1-2[8704:20b] reusing cell for row 19...</div><div><br /></div><div><div>Wow, now there is a lot of reusing going on! It seems as if the cells that appears for the first time when scrolling also are resued. That's why you see that rows 12 to 19 are reused. If you're really observant, you'll also notice something strange in the simulator. The text "this is row 0" appears in more than one place! What a nasty bug!! Hey, take it easy, it might not be a bug, just some proof of that the cell really is reused.</div><div><br /></div><div><span class="Apple-style-span" style="font-size: large;">Insights into the reuse scheme</span></div><div><br /></div><div>If the text "this is row 0" had appeared for all reused cells, I would have been able to explain this, but now I don't really understand it. I'm guessing it has to do with the fact that we're creating cells for row 0 to 11 with the same identifier. After that we start reusing cells and since there are more than one cell with the same identifier we sometimes get the row-0 instance, sometime the row-1 instance, and so on up to row-11 instance. After that we get the row-0 instance again, and so on.</div><div><br /></div><div>If we modify the 'tableView:cellForRowAtIndexPath' to make output the cell 'reuseIdentifier' property as well as the pointer to the object we see that this guess might be true.</div><div><br /></div><div>- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {</div><div><br /></div><div><span class="Apple-tab-span" style="white-space:pre"> </span>// try to retrieve "cell1" from the UITableView cache</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell1"];</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>if ( cell == nil ) {</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>// "cell 1" wasn't present in the cache, so create it</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>cell = [[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:@"cell1"];</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>NSLog(@"creating cell '%@' (%p) for row %d...", cell.reuseIdentifier, cell, indexPath.row);</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>} else {</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>// "cell 1" was present in the cache, so log that we're reusing it</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>NSLog(@"reusing cell '%@' (%p) for row %d...", cell.reuseIdentifier, cell, indexPath.row);</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>}</div><div><br /></div><div><span class="Apple-tab-span" style="white-space:pre"> </span>if ( indexPath.row == 0 ) { </div><div><span class="Apple-tab-span" style="white-space:pre"> </span>// assign a text to row 0</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>cell.text = [NSString stringWithFormat:@"this is row %d", indexPath.row];</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>}</div><div><span class="Apple-tab-span" style="white-space:pre"> </span></div><div><span class="Apple-tab-span" style="white-space:pre"> </span>return cell;</div><div>}</div><div><br /></div><div>If you run the modified version you'll see somthing like this:</div><div><br /></div><div>2009-05-24 20:12:51.907 Table1-2[8919:20b] creating cell 'cell1' (0x5284c0) for row 10...</div><div>2009-05-24 20:12:51.917 Table1-2[8919:20b] creating cell 'cell1' (0x5293c0) for row 9...</div><div>2009-05-24 20:12:51.929 Table1-2[8919:20b] creating cell 'cell1' (0x5287e0) for row 8...</div><div>2009-05-24 20:12:51.930 Table1-2[8919:20b] creating cell 'cell1' (0x526560) for row 7...</div><div>2009-05-24 20:12:51.931 Table1-2[8919:20b] creating cell 'cell1' (0x5292f0) for row 6...</div><div>2009-05-24 20:12:51.934 Table1-2[8919:20b] creating cell 'cell1' (0x528940) for row 5...</div><div>2009-05-24 20:12:51.937 Table1-2[8919:20b] creating cell 'cell1' (0x5297d0) for row 4...</div><div>2009-05-24 20:12:51.938 Table1-2[8919:20b] creating cell 'cell1' (0x520970) for row 3...</div><div>2009-05-24 20:12:51.939 Table1-2[8919:20b] creating cell 'cell1' (0x529940) for row 2...</div><div>2009-05-24 20:12:51.940 Table1-2[8919:20b] creating cell 'cell1' (0x529a60) for row 1...</div><div>2009-05-24 20:12:51.941 Table1-2[8919:20b] creating cell 'cell1' (0x529be0) for row 0...</div><div>2009-05-24 20:13:08.380 Table1-2[8919:20b] creating cell 'cell1' (0x52e290) for row 11...</div><div>2009-05-24 20:13:09.190 Table1-2[8919:20b] reusing cell 'cell1' (0x529be0) for row 12...</div><div>2009-05-24 20:13:09.517 Table1-2[8919:20b] reusing cell 'cell1' (0x529a60) for row 13...</div><div>2009-05-24 20:13:52.790 Table1-2[8919:20b] reusing cell 'cell1' (0x529940) for row 2...</div><div>2009-05-24 20:13:53.415 Table1-2[8919:20b] reusing cell 'cell1' (0x529940) for row 14...</div><div>2009-05-24 20:13:53.511 Table1-2[8919:20b] reusing cell 'cell1' (0x520970) for row 15...</div><div>2009-05-24 20:13:54.068 Table1-2[8919:20b] reusing cell 'cell1' (0x5297d0) for row 16...</div><div>2009-05-24 20:13:54.106 Table1-2[8919:20b] reusing cell 'cell1' (0x528940) for row 17...</div><div>2009-05-24 20:13:54.568 Table1-2[8919:20b] reusing cell 'cell1' (0x5292f0) for row 18...</div><div>2009-05-24 20:13:54.635 Table1-2[8919:20b] reusing cell 'cell1' (0x526560) for row 19...</div><div>2009-05-24 20:13:54.652 Table1-2[8919:20b] reusing cell 'cell1' (0x5287e0) for row 20...</div><div>2009-05-24 20:13:54.710 Table1-2[8919:20b] reusing cell 'cell1' (0x5293c0) for row 21...</div><div>2009-05-24 20:13:54.743 Table1-2[8919:20b] reusing cell 'cell1' (0x5284c0) for row 22...</div><div>2009-05-24 20:13:54.776 Table1-2[8919:20b] reusing cell 'cell1' (0x52e290) for row 23...</div><div>2009-05-24 20:13:54.810 Table1-2[8919:20b] reusing cell 'cell1' (0x529be0) for row 24...</div><div>2009-05-24 20:13:54.860 Table1-2[8919:20b] reusing cell 'cell1' (0x529a60) for row 25...</div><div>2009-05-24 20:13:54.893 Table1-2[8919:20b] reusing cell 'cell1' (0x529940) for row 26...</div><div>2009-05-24 20:13:54.943 Table1-2[8919:20b] reusing cell 'cell1' (0x520970) for row 27...</div><div>2009-05-24 20:13:54.993 Table1-2[8919:20b] reusing cell 'cell1' (0x5297d0) for row 28...</div><div>2009-05-24 20:13:55.060 Table1-2[8919:20b] reusing cell 'cell1' (0x528940) for row 29...</div><div>2009-05-24 20:13:55.126 Table1-2[8919:20b] reusing cell 'cell1' (0x5292f0) for row 30...</div><div>2009-05-24 20:13:55.193 Table1-2[8919:20b] reusing cell 'cell1' (0x526560) for row 31...</div><div>2009-05-24 20:13:55.293 Table1-2[8919:20b] reusing cell 'cell1' (0x5287e0) for row 32...</div><div>2009-05-24 20:13:55.393 Table1-2[8919:20b] reusing cell 'cell1' (0x5293c0) for row 33...</div><div>2009-05-24 20:13:55.543 Table1-2[8919:20b] reusing cell 'cell1' (0x5284c0) for row 34...</div><div>2009-05-24 20:13:55.726 Table1-2[8919:20b] reusing cell 'cell1' (0x52e290) for row 35...</div><div>2009-05-24 20:13:56.043 Table1-2[8919:20b] reusing cell 'cell1' (0x529be0) for row 36...</div><div>2009-05-24 20:13:57.194 Table1-2[8919:20b] reusing cell 'cell1' (0x529a60) for row 37...</div><div><br /></div><div>If you study the output closely - especially the pointers within the parentheses - you'll see that the cells are reused in a recurring pattern. This suggests that all the 'cell1' UITableViewCell objects are placed on a circular queue inside UITableView. Once a cell has been removed from the head of the queue and reused it is placed at the tail of the queue again. That a queue is used in the "cache" implementation is further suggested by the method name 'dequeueReusableCellWithIdentifier' since "dequeue" is a fancier way of saying "remove from the head".</div><div><br /></div><div><span class="Apple-style-span" style="font-size: large;">Resetting cell contents before reuse</span></div><div><br /></div><div>The solution to all these "strange" problems is to reset the contents of a reused cell before returning it to the table view. In our case that means setting the 'text' property every time - both for newly created cells and for reused cells.</div><div><br /></div><div><div>- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {</div><div><br /></div><div><span class="Apple-tab-span" style="white-space:pre"> </span>// try to retrieve "cell1" from the UITableView cache</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell1"];</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>if ( cell == nil ) {</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>// "cell 1" wasn't present in the cache, so create it</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>cell = [[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:@"cell1"];</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>NSLog(@"creating cell '%@' (%p) for row %d...", cell.reuseIdentifier, cell, indexPath.row);</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>} else {</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>// "cell 1" was present in the cache, so log that we're reusing it</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>NSLog(@"reusing cell '%@' (%p) for row %d...", cell.reuseIdentifier, cell, indexPath.row);</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>}</div><div><br /></div><div><span class="Apple-tab-span" style="white-space:pre"> </span>// reset cell contents<br /></div><div><span class="Apple-tab-span" style="white-space:pre"> </span>cell.text = [NSString stringWithFormat:@"this is row %d", indexPath.row];</div><div><span class="Apple-tab-span" style="white-space:pre"> </span></div><div><span class="Apple-tab-span" style="white-space:pre"> </span>return cell;</div><div>}</div><div><br /></div></div><div><span class="Apple-style-span" style="font-size: x-large;">Summary</span><br /></div><div><br /></div><div>Ok, now we have digged further down into the world of the UITableView class and hopefully it's even less frightening now that we have examined how the cell reuse scheme works. We're still very sloppy with our memory management - in fact, we're completely ignoring it - so bear in mind that these small tests are just that - small tests to increase our understanding.</div></div><div><br /></div></div></div></div>henrikbhttp://www.blogger.com/profile/05536290915355204969noreply@blogger.com0tag:blogger.com,1999:blog-3970047569105596105.post-7224793926794778672009-05-23T06:50:00.010+02:002009-11-04T08:20:33.847+01:00iPhone tutorial: UITableView from the ground up, part 1In a <a href="http://humblecoder.blogspot.com/2009/04/iphone-tutorial-navigation-based_24.html">previous post</a> (which I recommend that you read), I wrote quite a lot about UITableView, but I also mentioned that I would probably return to the subject in the future. Now that time has come! The reason for this is that I'm still a bit scared of the class and always try to avoid it when designing user interfaces. Sure, there are merits in fitting all information on a single screen since it forces you to really think about how to interact with your application and how to minimise the number of settings. However, understanding a powerful class like UITableView also has its merits since it can save you a lot of time during both the design and implementation phases. Furthermore, it gives the user a consistent experience over all iPhone applications instead of having to learn each application's interface.<div><br /></div><div>Avoiding the UITableView isn't that difficult as long as you're working with small pieces of information, but what do you do if you have a list of a hundred things you want to display? Choose a really small font? Use a tab controller with five tabs showing 20 things per tab? Use a navigation controller with "Next"-buttons? Use a UIScrollView? As you can see, there is no shortage of alternative solutions and some can probably work very well, but you should probably have a very strong reason for inventing something new opposed to using the UITableView since this is designed to show long lists of information.</div><div><br /></div><div>As you can see in the title of this post, this is part 1 of a series of UITableView tutorials. This is because the subject is huge and I think it will be easier to understand piece by piece instead of all at once even if it means that some overview is lost. To compensate for the lost overview I will try to post the parts in quite tight succession.</div><div><br /></div><div>Let's create the XCode project for this tutorial! Start XCode and choose "File/New project" from the menu. Select the "Window-Based Application" template and name it "Table1".</div><div><br /></div><div><span class="Apple-style-span" style="font-size:x-large;">Resources/MainWindow.xib</span></div><div><br /></div><div>Double-click on MainWindow.xib to start Interface Builder (IB) and load the xib-file. As always when doing anything but the most trivial tasks in IB, I recommend that you switch to hierarchical view mode by pressing the middle icon above the "View mode" text in the upper left corner. Bring up the library window in IB (CMD-L) and drag the "Table View" icon from the "Data views" section and drop it onto the "Window" object in the MainWindow.xib window. A small arrow should appear to the left of the "Window" object to indicate that other objects are embedded into it. Press the arrow and the hierarchy under the object should appear; in this case just the "Table View" object. If you double-click the "Table view" object the "design window" for that object appears, in this case the design window shows a UITableView with a list of cities in California.</div><div><br /></div><div>Now we should make the "Table1 App Delegate" object the delegate of the "Table View" object, so CTRL-drag from "Table View" to "Table1 App Delegate", first to connect it to the 'dataSource' outlet and then to the 'delegate' outlet.</div><div><br /></div><div><span class="Apple-style-span" style="font-size:x-large;">Classes/Table1AppDelegate.h</span><br /></div><div><br /></div><div>Above, we connected the application delegate object to both the 'delegate' and the 'data source'for our table, so let's specify that this class is going to implement the UITableViewDelegate and UITableViewDataSource protocols. We do this to allow the compiler to help us check that we have implemented all the required methods. Do this by editing the h-file so that the interface-line looks like this:</div><div><br /></div><div><div>@interface Table1AppDelegate : NSObject <UIApplicationDelegate, UITableViewDelegate, UITableViewDataSource><uiapplicationdelegate, uitableviewdatasource=""> {</uiapplicationdelegate,></div><div><br /></div><div>If you build the project (CMD-B), you'll see that the compiler issues the following warnings:</div><div><br /></div><div><div>Table1AppDelegate.m:29: warning: incomplete implementation of class 'Table1AppDelegate'</div><div>Table1AppDelegate.m:29: warning: method definition for '-tableView:cellForRowAtIndexPath:' not found</div><div>Table1AppDelegate.m:29: warning: method definition for '-tableView:numberOfRowsInSection:' not found</div><div>Table1AppDelegate.m:29: warning: class 'Table1AppDelegate' does not fully implement the 'UITableViewDataSource' protocol</div><div><br /></div><div>From this output we can immediately see that we need to implment the 'tableView:cellForRowAtIndexPath:' and 'tableView:numberOfRowsInSection' methods of the UITableViewDataSource protocol. I wrote quite a lot about these methods in <a href="http://humblecoder.blogspot.com/2009/04/iphone-tutorial-navigation-based_24.html">my first UITableView post</a> so I won't do a full "background" here, but instead focus on the implementation.</div><div><br /></div><div>If you want to find out what happens if you ignore this warning and refrain from implementing them, go ahead and build and run (CMD-Return) the application and watch it crash in the simulator. After the crash, check out the XCode console window (CMD-R) to see what went wrong. You'll see something like this:</div><div><br /></div><div><div>[Session started at 2009-05-23 09:58:36 +0200.]</div><div>2009-05-23 09:58:40.887 Table1-2[7110:20b] *** -[Table1AppDelegate tableView:numberOfRowsInSection:]: unrecognized selector sent to instance 0x523870</div><div>2009-05-23 09:58:40.893 Table1-2[7110:20b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[Table1AppDelegate tableView:numberOfRowsInSection:]: unrecognized selector sent to instance 0x523870'</div><div><br /></div><div>It seems someone tried to call 'numberOfRowsInSection', which wasn't implemented ("unrecognized selector sent to instance...").</div></div><div><br /></div><div><div><span class="Apple-style-span" style="font-size:x-large;">Classes/Table1AppDelegate.m</span></div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;">numberOfRowsInSection</span></div><div><br /></div><div>We'll start with 'numberOfRowsInSection' since that's the most basic of the two. It lays the foundation for the table by specifying how many rows it should have, which in our first example will be three (3). Add the following to the end of the file.</div><div><br /></div><div><div>- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {</div><div><span class="Apple-tab-span" style="white-space: pre;"> </span>return 3;</div><div>}</div></div><div><br /></div><div>When I first saw this method I really thought that there was some magic going on - I couldn't grasp what the stuff before the 'numberOfRowsInSection' part of the definition meant. My initial thoughts was that perhaps this method returns two things; a UITableView pointer as well as an NSInteger? Or is there some method overloading or other inheritance-realted Objective C-magic going on?</div></div><div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;">A short digression on Objective C method names</span></div><div><br /></div></div><div><div><div> Once the chock of the strange syntax had worn off , I realised that this method just have a rather strange name. Or actually, you could say it has no name at all - all it has is a list of argument names! If we break it down, it looks like this:</div><div><br /></div></div></div><div><div>- (NSInteger)</div><div>tableView:(UITableView *)tableView<br /></div><div>numberOfRowsInSection:(NSInteger)section {<br /></div><div><br /></div></div><div>So there are three components; the first is the return value, the second is argument 1 and the third is argument 2. Each argument is composed of a "label" or short descrptive text ("tableView" and "numberOfRowsInSection"), a colon (':') and the argument name ("tableView" and "section"). Well, that is not the full truth. It could also be broken down like this:</div><div><br /></div><div><div>- (NSInteger)</div><div>tableView</div><div>:(UITableView *)tableView<br /></div><div>numberOfRowsInSection:(NSInteger)section {<br /></div><div><br /></div></div><div><div>Here we have four components; return value, method name, argument 1 and argument 2. Here the "label" for argument 1 ("tableView") is instead used to specify the method name.</div><div><br /></div><div>Confusing, huh? In fact, this is all pretty standard Objective C - the method name and first argument label/descriptive text are fused into one - but what's "wrong" with this method in my view is the choice of the method name and/or argument label names. However, there actually exists an acceptable explanation for this naming scheme. Lot's of methods in "delegation" protocols are named according to this scheme and the reason for that is to provide the "delegation context" as the first argument. This is because a single object could be the delegate for several tables for example. If so, the delegate object needs to find out which table is requesting information from it. That's why the first argument is a UITableView pointer.</div><div><br /></div><div>A more intutive name for this method could have been achieved by rearranging the components a bit:</div><div><br /></div><div><div>- (NSInteger)numberOfRowsInSection:(NSInteger)section tableView:(UITableView *)tableView {</div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;">cellForRowAtIndexPath</span></div><div><br /></div><div>After the digression on Objective C method names above you probably understand why I left out the tableView "prefix" of the method name in the title above - it's so much easier to refer to the method by just saying 'cellForRowAtIndexPath' even though it's correct name is 'tableView:cellForRowAtIndexPath' since it is defined like this:</div><div><br /></div><div>- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {<br /></div><div><br /></div><div>This method is called by the table to create the objects used to display the contents of a cell (row) in the table, that is, objects of the class UITableViewCell. Let's do just that - create an UITableViewCell object and return it by adding the following to the end of the file.</div><div><br /></div><div><div>- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {</div><div><span class="Apple-tab-span" style="white-space: pre;"> </span>UITableViewCell *cell = [[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:nil];</div><div><span class="Apple-tab-span" style="white-space: pre;"> </span>cell.text = @"my cell";<br /></div><div><span class="Apple-tab-span" style="white-space: pre;"> </span>return cell;</div><div>}</div><div><br /></div><div>Two things are worth explaining here, or rather worth mentioning since the explanation is available in the API docs for 'initWithFrame:reuseIdentifier' in the UITableViewCell class. What I'm talking about is 'CGRectZero' which is used to specify the size of the cell and the 'nil' value for the 'reuseIdentifier'. We pass 'CGRectZero' since the API docs tells us to and 'nil' since we don't want to reuse the cell. By the way, we'll talk about reusing cells in the next part of this tutorial.</div><div><br /></div><div>Build and run (CMD-Return) in XCode and you should see a table containing three rows, all containing the text "my cell". Try touching the rows and they should turn blue when they are selected. If you try to select the fourth row, nothing will happens since our table only has three "active" rows. Maybe not that impressive, but also not that complicated to make, huh?</div><div><br /></div><div><span class="Apple-style-span" style="font-size:x-large;">Summary</span></div><div><br /></div><div>We have managed to display a table view with just a few lines of code and have hopefully managed to overcome some of our fear of table views. As always in my tutorials, I do "cheat" a little to keep things simple. For example, I do know that proper memory management is very important, but at this point it would just be in the way and introduce unwanted complexity. Furthermore, it's often quite easy to add once you understand the underlying classes you're working with.</div></div></div></div></div></div>henrikbhttp://www.blogger.com/profile/05536290915355204969noreply@blogger.com6tag:blogger.com,1999:blog-3970047569105596105.post-70890757267477276272009-05-13T16:57:00.011+02:002009-05-15T08:09:22.370+02:00iPhone tutorial: Storing and retrieving information using plists<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj7IPWFvJDpWQniXyzYweImxtLnoWUUrBL3MxQSzyn6_Z9aE4XGvqG0nHDKyBPigTq75wSKOnpooCPq-hYpr6KqJcfrb0stFlX10n_A9o0LwzMBIXcrKMNLCg9m4lrRJk140Am4m4MVx6I/s1600-h/foo.plist.png"><img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;width: 320px; height: 158px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj7IPWFvJDpWQniXyzYweImxtLnoWUUrBL3MxQSzyn6_Z9aE4XGvqG0nHDKyBPigTq75wSKOnpooCPq-hYpr6KqJcfrb0stFlX10n_A9o0LwzMBIXcrKMNLCg9m4lrRJk140Am4m4MVx6I/s320/foo.plist.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5335928451832032850" /></a><br />Some applications need to store information in order to be really valuable for the user. Others do it just for fun; for example, storing the highscores in a game. Yet others do it to offer a multi-platform experience, by making it possible to access the same information from several different platforms (web, desktop, mobile), where the iPhone might be one of them. In the last case, the information should probably be stored on a server on the Internet to make it really useful.<div><br /></div><div>There are several ways to store information persistently on the iPhone, but in this tutorial we're going to focus on "plists", or property lists as they really are called. If you already have done some iPhone development or if you've read the previous tutorials, you've already come in contact with property lists. That's because the Resources/Info.plist file present in all XCode template projects is a property list.</div><div><br /></div><div>If you click on it in XCode, the contents of the file will be presented in a "plist-editor" view, which makes it easy both to browse the information in the plist as well as edit it. If you instead open the file in an editor which understans xml (for example Dashcode), you'll see that Info.plist is an xml file.</div><div><br /></div><div>Ok, so a plist seems to be a standardised xml format for storing information on the iPhone. That's good, but what kind of format can be stored? Unless you have very specific needs, I would say almost anything. Plists basically contain a collection of key-value pairs, where the keys have to be unique and the values have to be objects. That is, you cannot store primitive types like 'int' and 'long' without first wrapping them in an Objective C object. You cannot store any Objective C object, though, but you come a long way with the ones that are supported:</div><div><br /></div><div>NSArray</div><div>NSDictionary</div><div>NSString</div><div>NSData</div><div>NSDate</div><div>NSNumber(intValue)</div><div>NSNumber(floatValue)</div><div>NSNumber(boolValue == YES or boolValue == NO)</div><div><br /></div><div>We said that plists contain a collection of key-value pairs. Such collections are often implemented using data types such as hash tables, hash maps or dictionaries, which more or less are the "same thing", but can also be implemented using arrays. When an array is used, the array indices are the keys, while, in the dictionary case, the keys often are strings. In the list of supported objects above, you can see that NSDictionary and NSArray present. This is extra interesting since a plist itself often is implemented using a dictionary or array object, called the root object.</div><div><br /></div><div>If you want to read more about property lists at this stage I recommend that you open the API docs browser in XCode and do a full text search for "property list". Then you should find a document called "Property List Programming Guide".</div><div><br /></div><div><span class="Apple-style-span" style="font-size: x-large;">A simple plist application</span></div><div><br /></div><div>Now let's get down to programming! Start a new "Window-Based" project in XCode and name it "plist1", by choosing "File/New project" from XCode's menu. We're not going to do anything "graphical" in the tutorial, but we'll use a window-based project anyway because it's an easy way to create a new project.</div><div><br /></div><div><span class="Apple-style-span" style="font-size: large;">Resources/foo.plist</span></div><div><br /></div><div>How to create a new plist-file in XCode isn't totally obvious, even though it is very easy once you find out how. CTRL-click on the "Resources"-folder/group in XCode and choose "Add/New file" from the pop-up menu. In the template window that appears, choose the "Other" section under the "Mac OS X" heading. There you will be able to select "Property list" - do that and name the file "foo". If you click on Resources/foo.list the plist-editor will appear and you'll see that the plist is empty, apart from the root object which we mentioned above. You'll also see that it is of type 'Dictionary'.</div><div><br /></div><div>If you click on the small icon at the end of the "Root"-line (to the right) a new line will appear under the root object, indented one level to indicate that it is contained inside the root object. The name of the key is set to "New item", but change this to "key1". We're happy with the default type - String - since we're going to use a string for our first test. Set the value to the text "value1" though, by double-clicking in the value-column.</div><div><br /></div><div>If you build the project (CMD-B) - an easy way to save all files! - and locate "foo.plist" on your hard disk using 'Finder' in Mac OS X, you'll see that it opens in 'Property List Editor' when you double-click it. That editor looks just like the one that's used in XCode if you click on the file there. If you instead open "foo.plist" with an xml-capable editor - like 'Dashcode' in Mac OS X - you'll instead see that it contains xml (see the picture).</div><div><br /></div><div>As you can see, the file actually contains xml. First comes some standard xml boilerplate stuff and after that, there is an opening "tag" called 'plist'. Inside that is the root-node which is of the type 'dict' (NSDictionary). Inside the root node is a 'key'-tag containing 'key1' and a 'string'-tag containing 'value1'. What all this means is that this file contains a plist, which contains a dictionary, which contains the key-value pair "key1=value1" where the value is of the type 'string' (NSString). Pretty self-explanatory, huh?<br /></div><div><br /></div><div><span class="Apple-style-span" style="font-size: large;">Classes/Plist1AppDelegate.m</span></div><div><br /></div><div>We're going to "hijack" the 'applicationDidFinishLaunching' method for our short test, so edit the method to make it look like below. The inlined comments tries to explain what's going on, so we won't analyse the code any further.</div><div><br /></div><div><div>- (void)applicationDidFinishLaunching:(UIApplication *)application { </div><div><br /></div><div> // Override point for customization after application launch</div><div> [window makeKeyAndVisible];</div><div><br /></div><div><span class="Apple-tab-span" style="white-space:pre"> </span>// create a pointer to a dictionary</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>NSDictionary *dictionary;</div><div><span class="Apple-tab-span" style="white-space:pre"> </span></div><div><span class="Apple-tab-span" style="white-space:pre"> </span>// read "foo.plist" from application bundle</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>NSString *path = [[NSBundle mainBundle] bundlePath];</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>NSString *finalPath = [path stringByAppendingPathComponent:@"foo.plist"];</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>dictionary = [NSDictionary dictionaryWithContentsOfFile:finalPath];</div><div><br /></div><div><span class="Apple-tab-span" style="white-space:pre"> </span>// dump the contents of the dictionary to the console</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>for (id key in dictionary) {</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>NSLog(@"bundle: key=%@, value=%@", key, [dictionary objectForKey:key]);</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>}</div><div>}</div><div><br /></div></div><div><span class="Apple-style-span" style="font-size: large;">Test run</span><br /></div><div><br /></div><div>Build and run in XCode (CMD-Return) and wait until the iPhone simulator appears. Once it does, click on the source code window in XCode and press CMD-R to bring the console window to the front. In it you should see something like this:</div><div><br /></div><div>2009-05-14 07:30:07.113 Plist1[659:20b] bundle: key=key1, value=value1<br /></div><div><br /></div><div>Pretty cool! With just a few lines of code, we were able to read an xml file containing a dictionary and dump the contents of the dictionary to the console. Reading a file from the application bundle which we did here, is an ideal way of handling "static data" like a default configuration or why not the initial high score table of a game? Whenever the user wants to "reset to defaults", you just have to read the data from the bundle again.<br /></div><div><br /></div><div><span class="Apple-style-span" style="font-size: x-large;">Saving a plist to a file</span></div><div><br /></div><div>Being able to save the contents of a plist to a file is of course very valuable and very useful. It allows you store "dynamic data" produced by your application to make it available to the user the next time the application is started. If it is the highscore table of a game you're saving, it's essential if the player wants to show the highscores to a friend. If not, the player would have to keep the application running until he or she meets the friend. That's far from practical, especially considering the rather short battery life of the iPhone ;)</div><div><br /></div><div><span class="Apple-style-span" style="font-size: large;">Classes/Plist1AppDelegate.m</span></div><div><br /></div><div>Replace the "create a pointer to a dictionary" code at the beginning of the 'applicationDidFinishLaunching' method we created above with the following:</div><div><br /></div><div><div><span class="Apple-tab-span" style="white-space:pre"> </span>// create a pointer to a mutable dictionary</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>NSMutableDictionary *dictionary;</div><div><br /></div><div>We're changing the class of the dictionary from NSDictionary to NSMutableDictionary in order to be able to make changes to the dictionary. (Mutable is a fancy way of saying that something can change, or "mutate".)</div><div><br /></div></div><div><div>Then add the following to the end of the 'applicationDidFinishLaunching':</div><div><br /></div></div><div><div><span class="Apple-tab-span" style="white-space:pre"> </span>// create a NSNumber object containing the</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>// integer value 2 and add it as 'key2' to the dictionary.</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>NSNumber *number = [NSNumber numberWithInt:2];</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>[dictionary setObject:number forKey:@"key2"];</div><div><span class="Apple-tab-span" style="white-space:pre"> </span></div><div><span class="Apple-tab-span" style="white-space:pre"> </span>// dump the contents of the dictionary to the console</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>for (id key in dictionary) {</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>NSLog(@"memory: key=%@, value=%@", key, [dictionary objectForKey:key]);</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>}<span class="Apple-tab-span" style="white-space:pre"> </span></div><div><br /></div><div><span class="Apple-tab-span" style="white-space:pre"> </span>// write xml representation of dictionary to a file</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>[dictionary writeToFile:@"/Users/henrik/Sites/foo.plist" atomically:NO];</div><div><br /></div><div>What we do here is add a key-value pair to the dictionary (this is possible since we now have a NSMutableDictionary). Both the key and the value has to be objects, so we create a NSNumber object containing the integer 2 and associate it with a NSString object containing the text "key2". To verify that the new key-value pair was added, we once again dump the contents of the dictionary to the console. After that comes the exciting stuff. With a single method call 'writeToFile' on our 'dictionary' object, the contents of the plist is written in xml format to a file of our choice. In this case we chose to save it in a directory called 'Sites' in my home directory. You should replace 'henrik' with your Mac OS X username to make it work on your computer.</div><div><br /></div><div><span class="Apple-style-span" style="font-size: large;">Test run</span></div><div><br /></div><div>Build and run in XCode (CMD-Return), wait for the iPhone simulator to start and then go back to XCode and press CMD-R to bring the console to the front. There you should see the following:</div><div><br /></div><div><div>2009-05-14 17:29:09.064 Plist1[863:20b] bundle: key=key1, value=value1</div><div>2009-05-14 17:29:09.075 Plist1[863:20b] memory: key=key1, value=value1</div><div>2009-05-14 17:29:09.075 Plist1[863:20b] memory: key=key2, value=2</div><div><br /></div><div>The first line contains the same information as in the previous test run. The second and third lines dump the contents of our changed (mutated) dictionary and thus we see that the key-value pair we added also shows up (key2). The first line is prefixed with "bundle:" to indicate that it shows the contents of the plist read from the bundle, while the other lines are prefixed with "memory:" to indicate that they show the contents of the plist in the iPhones memory - the one we mutated...</div><div><br /></div><div>Let's take a look the contents of the file that was written to the "Sites"-directory in our Mac OS X home directory. Locate "foo.plist" in "Finder" and double-click on it to load it into the "Property List Editor" and you'll see that it now also contains the key-value pair we added programmatically in the source code above. You can also open it in an xml capable editor like "Dashcode" to verify that the key-value pair is xml encoded in a similar way as the key-value pair we added from the plist editor in XCode.</div><div><br /></div><div><span class="Apple-style-span" style="font-size: x-large;">Reading a plist from a web server</span></div><div><br /></div><div>Being able to read information from a web server in a simple way is a really powerful feature. Thankfully, this feature is available on the iPhone and extremely simple to use if the information you want to read can be contained in a plist.</div><div><br /></div><div><span class="Apple-style-span" style="font-size: large;">Configuring the web server in Mac OS X</span></div><div><br /></div><div>In order to test this, we need access to a web server. This is not a problem since Mac OS X comes pre-packaged with the Apache web server. All you have to do is enable it. You do this by going to the "System settings" in Mac OS X (the "gears" icon in the dock), select "Sharing" and enable "Web sharing". Please excuse me for a bit sketchy here, but I'm using a Swedish version of Mac OS X and therefore don't know the exact names of these settings in the English version. It's pretty simple though.</div><div><br /></div><div>When you're on the "web sharing" page in the system settings, you might notice that there also is an edit box where you can see/edit the network name of your computer. Under this edit box there also is a small text which says (freely translated): "Other computers on the local network can access your computer as Name.local", where "Name" is the name of your computer. My computer is called "MacBook", so I'll refer to that from now on.</div><div><br /></div><div>To test that your web server works, open up a web browser and type the following:</div><div><br /></div><div> http://MacBook.local/~henrik</div><div><br /></div><div>Once again, remember to replace "MacBook" with the name of your computer and replace "henrik" with the username you use in Mac OS X. If it works you should see a small text titled "Your web site" (freely translated). Now test the following URL instead:</div><div><br /></div><div><div> http://MacBook.local/~henrik/foo.plist</div><div><br /></div><div>Hey, that's the xml representation of our plist! The one we saved to the "/Users/henrik/Sites/foo.plist" path on our hard disk above. How did it end up in our web browser? It turns out that the "Sites"-directory in your home directory contains the files for your local web site.</div><div><br /></div><div>Now that we have set up and tested our local web site it becomes really easy to experiment with "web enabled" iPhone applications. Just place the files you would read from the web in your Sites-directory.</div><div><br /></div><div><span class="Apple-style-span" style="font-size: large;">Classes/Plist1AppDelegate.m</span><br /></div><div><br /></div><div>Reading a plist from a web server URL is really simple. Just add the following to the end of 'applicationDidFinishLaunching'</div><div><br /></div><div><div><span class="Apple-tab-span" style="white-space:pre"> </span>dictionary = [NSDictionary dictionaryWithContentsOfURL:[NSURL URLWithString:@"http://localhost/~henrik/foo.xml"]];</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>for (id key in dictionary) {</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>NSLog(@"web key=%@, value=%@", key, [dictionary objectForKey:key]);</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>}</div><div><br /></div><div><span class="Apple-style-span" style="font-size: large;">Test run</span><br /></div><div><br /></div><div>Build and run in XCode (CMD-Return) and you should see something like this in the console window:</div><div><br /></div><div><div>2009-05-15 07:40:40.691 Settings1[426:20b] bundle: key=key1, value=value1</div><div>2009-05-15 07:40:40.692 Settings1[426:20b] memory: key=key1, value=value1</div><div>2009-05-15 07:40:40.694 Settings1[426:20b] memory: key=key2, value=2</div><div>2009-05-15 07:40:40.719 Settings1[426:20b] url: key=key1, value=value1</div><div>2009-05-15 07:40:40.719 Settings1[426:20b] url: key=key2, value=2</div><div><br /></div><div>It's the last two lines - the ones prefixed with "url:" that are new compared to the last test run. Those lines come from the dump of the dictionary which we read from our web server url, and as you can see they are identicaly to the "memory:"-lines. That's because we wrote the memory-dictionary to a file in the "Sites"-directory of our home directory in Mac OS X and then read it back via the web server in Mac OS X.</div><div><br /></div><div><span class="Apple-style-span" style="font-size: x-large;">Summary</span></div><div><br /></div><div>Being able to access information stored in powerful data structures (hash tables, arrays) as easy is this is indeed a blessing, since creating proprietary file formats and then writing parsers for them, etc. can be quite time consuming. That it's equally easy to access information stored on a web server is fantastic since you can "Internet enable" your application with just a few lines of code. The only restriction with the methods presented in this tutorial is that the information as to be stored in the plist xml format , so you can't access any type of information this easily - just the information you have control over yourself.</div><div><br /></div><div><br /></div></div></div></div></div></div>henrikbhttp://www.blogger.com/profile/05536290915355204969noreply@blogger.com18tag:blogger.com,1999:blog-3970047569105596105.post-31021332240865206312009-05-09T21:07:00.007+02:002009-05-10T10:31:14.819+02:00iPhone tutorial: "Exitable" user interface, part 2 - delegation using a protocolHere is another unplanned "part 2". After writing the last post I felt that the "piece of magic" I used to make the tab bar controller disappear so that we would return to the "primary screen" was a bit too quick and dirtyish. If you don't remember what piece of magic I'm talking about it was this piece of code:<div><br /></div><div><div><span class="Apple-tab-span" style="white-space:pre"> </span>Multi2AppDelegate *multiAppDelegate = (Multi2AppDelegate *)[[UIApplication sharedApplication] delegate];</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>[multiAppDelegate buttonPressed:sender];</div><div><br /></div><div>Just that I refered to it as magic probably made you think that it wasn't completely kosher. In the iPhone and Objective C world you usually solve a problem like this using "delegation". If the word delegation sounds familiar to you, it's probably because most iPhone applications have a file where the word delegate appears in the name of the file. This, in turn, is because the UIApplication object has a 'delegate' property which it uses to inform another object of certain important things that are happening in the application.</div><div><br /></div><div>Well, we have a similar situation in our little application. We want to signal that we want to exit the secondary user interface - the tab bar and its view controllers - when the user presses the "Done"-button. This is also an important thing that happens and that another object want to know about. In our case we want to know about it in order to be able to make the secondary interface disappear so that we can return to the primary interface.</div><div><br /></div><div>Since the situations are similar, perhaps we should use "delegation" here as well? If we check the API docs for the UIApplication class in XCode we see that the 'delegate' property is defined like this:</div><div><pre></pre></div><div><pre></pre></div><div>@property(nonatomic, assign) id<UIApplicationDelegate><uiapplicationdelegate><uiapplicationdelegate> delegate<br /></uiapplicationdelegate></uiapplicationdelegate></div><div><div></div><div><div></div><div><br /></div></div></div><div>The type of the property is 'id', which is the Objective C way of saying that we want a pointer to a generic object. Notice that we don't have to use an astersik character (*) in the specification even though this is a pointer - that's a Objective C convenience thing. But what is the UIApplicationDelegate stuff between the less than and greater than characters? That's the name of a Objective C "protocol", which, simply put, is a set of methods that a class has to implement. So the strange looking declaration for 'delegate' can be interpreted as "delegate is a pointer to an object which implements the UIApplicationDelegate protocol".</div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;">Item1ViewController.h</span></div><div><br /></div><div>We want to define a 'delegate' property for our Item1ViewController class so that someone interested in knowing what's happening in the view controller can put a pointer to itself in the 'delegate' property. Edit the file so that it looks like this (some things are omitted here):</div><div><div><pre></pre></div><div><div><pre></pre></div><div><div><pre></div><div>@protocol Item1ViewControllerDelegate</div><div>-(void)doneButtonPressed;</div><div>@end</div><div><br /></div><div>@interface Item1ViewController : UIViewController {</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>id<Item1ViewControllerDelegate> delegate;</div><div>}</div><div><br /></div><div>@property (nonatomic, retain) IBOutlet id<Item1ViewControllerDelegate> delegate;</div><div><br /></div><div>-(IBAction)buttonPressed:(id)sender;</div><div><br /></div><div>@end</div><div><div></pre></div><div><br /></div></div></div></div><div><div><br /></div></div><div>In the beginning of the file we define a protocol called 'Item1ViewControllerDelegate', which contains a single method declaration for a method called 'doneButtonPressed'. Apparently, the only thing we think that someone might be interested to know about us is that the "Done"-button has been pressed. After this we declare a variable called 'delegate' which should point to an object implementing the protocol we just defined. We also make this variable into a property and mark it with IBOutlet so that we can manipulate it from Interface Builder (IB). The rest is unchanged from before.</div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;">Item1ViewController.m</span></div><div><br /></div><div>Since we added a property declaration in the h-file, we need to implement in the m-file. We do this by adding the following to the top of the file, right after the @implementation-line.</div><div><br /></div><div><pre></pre></div><div>@synthesize delegate;<br /></div><div><div></div><div><br /></div></div><div>After this, we should re-write the 'buttonPressed' method to make it look like this:</div><div><br /></div><div><div><pre></pre></div><div>-(IBAction)buttonPressed:(id)sender {</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>UIButton *button = (UIButton *)sender;</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>NSLog(@"Item1ViewController:buttonPressed:sender=%p, title=%@", sender, button.currentTitle);</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>if ( [button.currentTitle compare:@"Done"] == NSOrderedSame ) {</div><div>//<span class="Apple-tab-span" style="white-space:pre"> </span>Multi2AppDelegate *multiAppDelegate = (Multi2AppDelegate *)[[UIApplication sharedApplication] delegate];</div><div>//<span class="Apple-tab-span" style="white-space:pre"> </span>[multiAppDelegate buttonPressed:sender];</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>[delegate doneButtonPressed];</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>}</div><div>}</div><div><div></div><div><br /></div></div><div>We have commented out the two lines containing the UIApplication magic and replaced it with a single line which calls the 'doneButtonPressed' method of the 'delegate' object (pointer). Instead of calling a method of a specific object - the one that the UIApplication's 'delegate' property points to - we call the method of an unspecified object - the one that our 'delegate' property points to. This gives us much more freedom in choosing which object that will react to the pressing of the "Done"-button. Instead of having to use the UIApplication delegate object we can use any object.</div><div><br /></div><div>Using "callbacks" like this is often referred to as loose coupling, meaning that the sender and receiver of the message (caller and callee of the method) doesn't need to know that much about eachother. The only thing they do need to know about is that both implement the same protocol (or interface).</div><div><br /></div><div>If you wonder how we dared calling a method on the 'delegate' object before checking if it was set, that's because it's allowed to send messages to (call methods of) the "nil object".</div><div><br /></div><div><div><span class="Apple-style-span" style="font-size:large;">MultiAppDelegate.m</span></div><div><br /></div><div>The whole reason of introducing delegation in Item1ViewController was to be able to implement the "Done"-button functionality in any object instead of in the UIApplication's 'delegate' object. But, in order to keep this part of the tutorial short, we will use it anyway, so edit the 'buttonPressed' method and add a new method called 'doneButtonPressed'. After you edit the file, it should look like this:</div><div><br /></div><div><div><pre></pre></div><div>-(IBAction)buttonPressed:(id)sender {</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>UIButton *button = (UIButton *)sender;</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>NSLog(@"MultiAppDelegate: buttonPressed:sender=%p, title=%@", sender, button.currentTitle);</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>if ( [button.currentTitle compare:@"TabBar"] == NSOrderedSame ) {</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>[window addSubview:[tabBarController view]];</div><div>//<span class="Apple-tab-span" style="white-space:pre"> </span>} else if ( [button.currentTitle compare:@"Done"] == NSOrderedSame ) {</div><div>//<span class="Apple-tab-span" style="white-space:pre"> </span>[[tabBarController view] removeFromSuperview];</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>}</div><div>}</div><div><br /></div><div>// Item1ViewControllerDelegate protocol implementation</div><div><br /></div><div>-(void)doneButtonPressed {</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>NSLog(@"doneButtonPressed");</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>[[tabBarController view] removeFromSuperview];</div><div>}</div><div><div></div><div><br /></div></div><div>In 'buttonPressed', we have commented out the quick and dirty version of the code which removed the tab bar controller view if a button named "Done" was pressed. We have replaced this with an implementation of the Item1ViewControllerDelegate protocol, which consists of the single method 'doneButtonPressed'. We now removed the tab bar controller in this new method instead.</div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;">MainWindow.xib</span></div><div><br /></div><div>Since we have defined the MultiAppDelegate and Item1ViewController objects in the same IB-file, it becomes very easy to make the 'delegate' property of the ItemViewController point to MultiAppDelegate. Just CTRL-drag from the view controller to the app delegate and select the 'delegate' outlet in the window that pops up.</div><div><br /></div><div>That's it, we have replaced our quick and dirty solution with a very well behaved delegation implementation! Beautiful, and very professional looking, huh? Delegation can actually be used for other stuff than informing another object of what's going on. It can also be used to control another object, for example to instruct a generic object of what to display. That's how the 'dataSource' in an UITableView works, but that's outside the scope of this tutorial ;)</div></div></div></div></div></div>henrikbhttp://www.blogger.com/profile/05536290915355204969noreply@blogger.com3tag:blogger.com,1999:blog-3970047569105596105.post-76646136766135368072009-05-07T17:38:00.008+02:002009-05-08T16:44:29.037+02:00iPhone tutorial: Creating an "exitable" user interfacesMany iPhone applications have a single user interface which you cannot "exit" - it's displayed all the time. It can still be quite complex with multiple level navigation controllers and table views, multiple tabs in a tab controller, or even a combination of a tab bar controller and navigation controllers, but still, you see the tab / navigation bar all the time. There is no exit.<div><br /></div><div>The XCode "Utility Application" template, the iPhone stocks and weather applications are some examples of a different breed of applications. They have a "primary screen" which displays some information and might even allow you to interact with it. When you need to change a setting or something similar you bring up the "secondary screen", which is a traditional iPhone user interface with all the well known components. You can say that this kind of application has multiple interfaces, which you can switch between or exit from.</div><div><br /></div><div>In this tutorial, we're going to create a simple multi-interface application. It will consist of a primary screen with a single button on it which takes us to a secondary screen with a tab bar controller with a single tab. The view of the single tab will also have a single button which "exits" the interface and takes us back to the main screen.</div><div><br /></div><div>Start XCode and choose "File/New project" from the menu and create a "Window-Based Application" called "Multi". This will create a project with the following structure:</div><div><br /></div><div>Multi</div><div> Classes</div><div> MultiAppDelegate.[hm]</div><div> Resources</div><div> MainWindow.xib</div><div><br /></div><div><span class="Apple-style-span" style="font-size: x-large;">Primary screen</span></div><div><br /></div><div>To keep the project small, we'll use the application delegate to implement the primary screen. The delegate will also function as the central point of our application which switches between the primary and secondary screen. We will also "cheat" a little and won't use a view controller for this screen because it is so simple and the main intent with this tutorial is to demonstrate how to "exit" an user interface. The only thing the primary screen will have to handle is to detect that a button was pressed so that it can switch between the primary and secondary screen.</div><div><br /></div><div><span class="Apple-style-span" style="font-size: large;">MultiAppDelegate.h</span></div><div><br /></div><div><div>@interface MultiAppDelegate : NSObject <uiapplicationdelegate> {</uiapplicationdelegate></div><div><span class="Apple-tab-span" style="white-space:pre"> </span>UIWindow *window;</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>UITabBarController *tabBarController;</div><div>}<br /></div><div><br /></div><div>@property (nonatomic, retain) IBOutlet UIWindow *window;</div><div>@property (nonatomic, retain) IBOutlet UITabBarController *tabBarController;</div><div><br /></div><div>-(IBAction)buttonPressed:(id)sender;</div><div><br /></div><div>Edit the file to make it look like above. We added the 'tabBarController' pointer and made it into a property and marked it with IBOutlet to make it accessible from Interface Builder (IB). We'll use this to keep track of the tab bar controller we will create. We also added an IBAction called 'buttonPressed:'. Notice the colon (:) at the end of the name? That's Objective C's way of saying that this method takes one argument. Since we're dealing with an iPhone "action" this argument is the sender of the action, which in our case will be an UIButton.</div><div><br /></div><div><div><span class="Apple-style-span" style="font-size: large;">MultiAppDelegate.m</span></div><div><br /></div><div><div>Instruct the compiler to create the accessors (set/get) for our 'tabBarController' property by editing the top of the file to make it look like this:</div><div><br /></div><div>@synthesize window;</div><div>@synthesize tabBarController;</div><div><br /></div></div><div>Implement the IBAction method by editing the bottom of the file to make it look like this:</div><div><br /></div></div><div><div>-(IBAction)buttonPressed:(id)sender {</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>UIButton *button = (UIButton *)sender;</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>NSLog(@"MultiAppDelegate: buttonPressed:sender=%p, title=%@", sender, button.currentTitle);</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>if ( [button.currentTitle compare:@"TabBar"] == NSOrderedSame ) {</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>[window addSubview:[tabBarController view]];</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>} else if ( [button.currentTitle compare:@"Done"] == NSOrderedSame ) {</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>[[tabBarController view] removeFromSuperview];</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>}</div><div>}</div><div>@end</div><div><br /></div><div>The first line converts the generic 'sender' pointer into a specific pointer 'button' of the type UIButton. After that we add some diagnostic logging so that we can see (in the XCode console window) that the method has been called. Then comes an if-clause that checks the title of the button that was pressed. If it was "TabBar" we make the 'tabBarController' appear by adding its view to our window. If it was "Done" we remove the 'tabBarController' view from its superview by, quite logically, calling removeFromSuperview and thus make it disappear.</div><div><br /></div><div>Did you notice that we didn't do anything in 'applicationDidFinishLaunching'? That's where we usually makes our user interface appear by adding a subview to our window, but now that won't happen until we press a button.</div><div><br /></div><div><span class="Apple-style-span" style="font-size: x-large;">Secondary screen</span></div><div><br /></div><div>As we said initially, the secondary screen will consist of a tab bar controller with a single tab, or "item" as they are called in the iPhone, and thus a single view controller. Create the view controller's source code files by selecting the "Classes" group in XCode's "Groups & Files" section and choose "File/New file" from the menu. Select "UIViewController Subclass" and name the file Item1ViewController.m and ensure that "also create h-file" is enabled. This will create the following files.</div><div><br /></div><div><span class="Apple-style-span" style="font-size: large;">Item1ViewController.h</span></div><div><br /></div><div><div>@interface Item1ViewController : UIViewController {</div><div><br /></div><div>}</div><div><br /></div><div>-(IBAction)buttonPressed:(id)sender;</div><div><br /></div></div><div>The only change we made to this was to add the IBAction 'buttonPressed:' (once again notice the colon which indicates that this method takes one argument). This action will be generated by our "exit"-button which will take us back to the primary screen.</div><div><br /></div><div><div><span class="Apple-style-span" style="font-size: large;">Item1ViewController.m</span></div><div><br /></div><div>At the top of the file, add a a new import-statement under the existing import-statement:</div><div><br /></div><div>#import "MultiAppDelegate.h"<br /></div><div><br /></div><div>At the end of the file, just before the @end marker add the following:</div><div><br /></div><div><div><div><div>-(IBAction)buttonPressed:(id)sender {</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>UIButton *button = (UIButton *)sender;</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>NSLog(@"Item1ViewController:buttonPressed:sender=%p, title=%@", sender, button.currentTitle);</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>if ( [button.currentTitle compare:@"Done"] == NSOrderedSame ) {</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>Multi2AppDelegate *multiAppDelegate = (Multi2AppDelegate *)[[UIApplication sharedApplication] delegate];</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>[multiAppDelegate buttonPressed:sender];</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>}</div><div>}</div><div><br /></div></div></div><div>This method starts out in the same way as the identically named method in MultiAppDelegate.m; we create a 'button' pointer from the generic 'sender' pointer and log a diagnostic message to the XCode console window to indicate that the method has been called. We then check if the title of the pressed button was "Done" and if it was we do some magic!</div><div><br /></div><div>The intention of this magic is to get hold of a pointer to our MultiAppDelegate object. We do this by first getting hold of a pointer to our UIApplication object by calling a "class-method" of the UIApplication class called 'sharedApplication'. Once we have this pointer we can get hold of our UIApplication's delegate by calling 'delegate' on it. This, in turn, gives us another pointer - pointing to our MultiAppDelegate object - and since we know that our delegate is of the type MultiAppDelegate we cast the delegate pointer to that type. After this, we have a pointer to our MultiAppDelegate object in 'multiAppDelegate' - neat, huh?</div><div><br /></div><div>After that we can call MultiAppDelegate's method 'buttonPressed:' just as if we have had setup a property for it and connected the MultiAppDelegate object to it in IB. This would have been impossible in IB since we modularised the interface into two xib-files instead of one, so thanks Apple for making this magic possible ;)</div><div><br /></div><div>But why do we want to call 'buttonPressed:' in MultiAppDelegate when we could handle the buttonPressed action here in Item1ViewController? That's because only MultiAppDelegate has the possibility to remove the tab bar controller. Here in Item1ViewController, we're "one level down" and doesn't really know that we are inside a tab bar controller. Thus we need to "step up one level" where we have a better overview of our application and can see clearly that a tab bar controller actually exists. When going to UIApplication we actually do more than step up one level, we actually go to the absolute top of our application where we have a complete overview of everything that's going on. This makes UIApplication, or as in this case, it's delegate the perfect "central controlling point" of the application.</div><div><br /></div><div>Speaking of this, I stumbled across an old <a href="http://www.iphonedevsdk.com/forum/iphone-sdk-tutorials/2113-multiple-xib-how-3.html">discussion thread</a> debating how to do "central control" like this. It's quite informative, so put it on your reading list.</div><div><br /></div><div>Letting the "Done"-button inform both Item1ViewController and MultiAppDelegate instead of just MultiAppDelegate gives Item1ViewController a chance to "clean up and finish" before MuliAppDelegate removes it from its view.</div><div><br /></div><div><span class="Apple-style-span" style="font-size: x-large;">Designing the interfaces</span></div><div><br /></div><div>Now that we have created the source code for our classes, it's time to tie everything together in Interface Builder (IB) as well as add the buttons, tab controllers, etc. </div><div><br /></div><div><span class="Apple-style-span" style="font-size: x-large;">MainWindow.xib</span></div><div><br /></div><div>Double-click on Resources/MainWindow.xib in XCode to start IB and make it load the xib-file. As always, switch to "hierarchical view mode" in IB by pressing the middle button above the text "View mode" in the MainWindow.xib window.</div><div><br /></div><div><span class="Apple-style-span" style="font-size: large;">Button</span></div><div><br /></div><div>We start by adding the button which will make the secondary user interface appear. If you're really observant, you might have noticed that the title of this button has to be "TabBar" since that's what we're comparing against in the 'buttonPressed:' method in MultiAppDelegate. So, bring up the Library window (CMD-L) and drag a "Rounded Rect Button" from the "Inputs & Values" section in the Library window and drop it on top of the Window-object in the MainWindow.xib window. The button should appear under the Window-object, indented one level and a small arrow should be added to the left of the Window-object to indicate that it "contains something".</div><div><br /></div><div>Double-click on the "Rounded Rect Button" object to bring up the edit window. The button should appear in the center of the window. Now double-click the button in the edit-window and enter the text "TabBar". Return to MainWindow.xib and CTRL-drag from "Rounded Rect Button" to "Mutli App Delegate" and release the mouse button. A window called 'Events' should pop-up and allow you to select 'buttonPressed:' (once again, notice the colon at the end). Select it, and you have made the "Multi App Delegate" object the target of the button's default action. This default action is sent whenever the button is pressed.</div><div><br /></div><div><span class="Apple-style-span" style="font-size: large;">Tab Bar Controller</span></div><div><br /></div><div>Now we should add the tab bar controller which will function as our secondary user interface, so drag a "Tab Bar Controller" from the "Controllers" section of the Library window to the MainWindow.xib window and drop it at the end of the list. Immediately expand the hierarchy under the controller by pressing the small arrow which is located to the left of the "Tab Bar Controller" object in the list in MainWindow.xib. See that it contains two view controllers? That's because a tab bar controller by default has two tabs, sorry, items. In this example we should only use one, so select the second view controller and press backspace.</div><div><br /></div><div>Our application delegate should handle this tab bar controller, so CTRL-drag from the delegate to the tab bar controller and connect the 'tabBarController' outlet/property.</div><div><br /></div><div><span class="Apple-style-span" style="font-size: x-large;">Item1ViewController.xib</span><br /></div><div><br /></div><div><div>To make things a bit more modular, we should create a separate xib for the view controller, or actually its contents; the views, buttons, etc. Do this by choosing "File/New" from the IB menu and select the "View" template. Start by saving the file by choosing "File/Save as" from the menu. Navigate to the directory containing your project files and save the file as "Item1ViewController" (the xib-extension will be added automatically). IB will bring up a window asking if you want to add the file to your project. We do so check the checkbox and click the Add-button.</div><div><br /></div><div>Go back to XCode and verify that the file has appeared in our project. It should be located at the end of the list of files under the "Multi" group - just above the "Targets"-group. We want it in the Resources group, so drag the xib-file into it. Build the project (CMD-B) and XCode prompts you to save the changes - do that.</div><div><br /></div><div>Return to IB by double-clicking on Resources/MainWindow.xib. Locate the MainWindow.xib window in IB and select the "Selected View Controller" object. Press CMD-1 to bring up the Attributes tab of the Inspector window and choose Item1ViewController from the "NIB Name" drop-down menu. This tells IB that the contents of this view controller should be loaded from that file. Thus, we shouldn't add any contents to the view controller here in MainWindow.xib. <br /></div><div><br /></div><div>However, if you expand the view controller by pressing the small arrow to the left of it, you'll see that it already contains a "Tab Bar Item" but that's ok because that object is a bit special. It's used to control the appearance of this view controller on the tab bar, so you could say that it's part of the configuration for the tab bar controller rather than the view controller.</div><div><br /></div><div>As the final modularisation step, select the "Selected View Controller", press CMD-4 and select "Item1ViewController" from the Class drop-down menu so that IB will create our UIViewController subclass instead of a generic UIViewController.</div><div><br /></div><div><span class="Apple-style-span" style="font-size: large;">Simulated metrics</span></div><div><br /></div><div>Locate the Item1ViewController.xib window in IB and select the "View" object. This view will be displayed "inside" a tab bar controller and will thus not have access to the full display of the iPhone, since the bottom part of the display will be covered by the tab bar controller. If we would have designed this view in MainWindow.xib instead of modularising it into its own xib-file this would have been handled automatically for us, but now we need to "simulate" it. Therefore, after selecting the "View" object, press CMD-1 and you'll see that there is a section called "View Simulated Bar Metrics", which is exactly what we're looking for. Choose "Tab Bar" from the "Bottom Bar" drop-down menu. If you double-click on the "View" object in the Item1ViewController.xib window you'll see that a an empty black bar now has appeared at the bottom of the edit-window. This helps us avoid this area when placing buttons, etc, since they otherwise would be obscured by the tab bar.</div><div><br /></div><div><span class="Apple-style-span" style="font-size: large;">"Exit" button</span></div><div><br /></div><div>While we have the edit-window at the front, we should take the opportunity to add our "Exit" button to the view. This button should tell the application that we're done using the secondary user interface and want to return to the primary. If you paid close attention to the source code of the 'buttonPressed:' methods in MultiAppDelegate.m and Item1ViewController.m you probably noticed that the title of this button should be "Done" since that's what we compare against. So drag a "Rounded Rect Button" from the "Inputs and Values" section of the Library Window (CMD-L) in IB into the edit-window of the "View" object (the one with the simulated tab bar at the bottom). After you have placed the button, double-click it and enter the text "Done".</div><div><br /></div><div><span class="Apple-style-span" style="font-size: large;">File's owner</span></div><div><br /></div><div>Item1ViewController.xib will be used to initialise our class ItemViewController, so we should change the class of the "File's owner" object to that class. Do this by selecting "File's owner", press CMD-4 and choose "Item1ViewController" from the Class drop-down menu. After selecting this, you should see that our action 'buttonPressed:' appears in the "Class actions" section just below the Class drop-down menu. Selecting the class of the "File's owner" thus gives us access to all the stuff we have defined for the class.</div><div><br /></div><div>That's good, because we need to connect the "View" object to our view controller's view-property, so CTRL-drag from "File's owner" to "View" and select 'view' in the window that pops-up.</div><div><br /></div><div>We also need to make "File's owner", or actually our Item1ViewController, the target of the "Done-button's" default action, so CTRL-drag from "Rounded Rect Button" to "File's owner" and select 'buttonPressed:' in the window that pops up.</div><div><br /></div><div><span class="Apple-style-span" style="font-size: x-large;">Test run</span></div><div><br /></div><div>Save everything in IB (CMD-S) and go back to XCode to build and run (CMD-Return). The application should appear in the iPhone simulator shortly and you should see the primary screen consisting of a white screen with a button called "TabBar". Press it and the secondary screen should appear, containing a tab bar controller with a single item should appear. In the view of the first item should be a button called "Done". Press it and you should return to the primary screen.</div><div><br /></div><div>If you take a look in XCode's console window (CMD-R), you should see something like this:</div><div><br /></div><div><div>[Session started at 2009-05-08 16:39:14 +0200.]</div><div>2009-05-08 16:39:20.278 Multi[28085:20b] MultiAppDelegate: buttonPressed:sender=0x525800, title=TabBar</div><div>2009-05-08 16:39:22.110 Multi[28085:20b] Item1ViewController:buttonPressed:sender=0x536e30, title=Done</div><div>2009-05-08 16:39:22.111 Multi[28085:20b] MultiAppDelegate: buttonPressed:sender=0x536e30, title=Done</div><div><br /></div><div>Notice how the "Done"-button generates two entries in the console log. The first is from the 'buttonPressed:' method in Item1ViewController and the second is from 'buttonPressed:' in MutlAppDelegate since Item1ViewController calles MultiAppDelegate after it has processed the action.</div></div></div></div></div></div></div>henrikbhttp://www.blogger.com/profile/05536290915355204969noreply@blogger.com0tag:blogger.com,1999:blog-3970047569105596105.post-71142796713627356122009-04-28T19:38:00.007+02:002009-04-28T21:02:01.350+02:00iPhone tutorial: Adding ui controls to a view and handle the events/actions<div>This tutorial could have been called "Navigation Controller from Scratch, part 2" since we're going to continue from the point where we ended that tutorial, but I decided against it since I wanted a more representative title.</div><div><br /></div><div>Originally, I didn't plan to write a part two, but I thought it was pretty boring to leave you with just a navigation controller and some view controllers. Therefore I thought, why not add some "controls" to make things more interesting? If you wonder what a control is, it is a collective name for buttons, sliders, etc. that are "used to convey user intent to the application" as the API docs for UIControl states it. Speaking about the API docs for UIControl, that document contains some valuable information for understanding how controls "communicate" with the applicaiton - especially the "The Target-Action Mechanism" section - so go ahead and read it right now if you want to.</div><div><br /></div><div>So, start by loading the "WinNav" project that we created in the last part into XCode. The first thing we're going to do is to undo some things we did in the first part. Double-click on Resources/MainWindow.xib to start Interface Builder (IB) and load the xib-file into it. As always, remember to check that the MainWindow.xib file in IB is in hierarchical view mode by clicking the middle button above the text "View mode" in the upper left corner of the window.</div><div><br /></div><div>Select "Win Nav App Delegate" and CTRL-click on it. This should pop up a window showing all the outlets, etc. of the object. Scroll down to the "Received Actions" section and disconnect the 'next' and 'next2' actions by pressing the small "x" to the left of the "Rounded Rect Button".</div><div><br /></div><div>If you are really observant you probably wondered why we never used the 'button' and 'button2' outlets in the last part, although we took ourselves the time to connect them. We did this to demonstrate how to make it possible, if required, to get a reference to an object created in IB by letting IB assign a value to a pointer that we defined in the source code.<br /></div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;">WinNavAppDelegate.h</span></div><div><br /></div><div>In this part we will need references to IB objects since we're going to play with controls that send "actions" to us (calls methods) and it's always nice to see exactly which object it was that sent us an action. So how do we find out which control that sent us an event? It's pretty simple since controls can send three "types" of actions to us, or actually provide 0, 1 or 2 arguments when calling the method associated with an event:</div><div><br /></div><div><div>- (void)action</div><div>- (void)action:(id)sender</div><div>- (void)action:(id)sender forEvent:(UIEvent *)event</div><div><br /></div><div>As you can see, one of the arguments is called 'sender' and that is actually the pointer to the object which sent the action. You probably remembered that we defined two actions - 'next' and 'next2' - in the last part using the following lines in WinNavAppDelegate.h:</div><div><br /></div><div><div>- (IBAction)next;</div><div>- (IBAction)next2;</div><div><br /></div><div>Neither of these take any arguments so we shoud edit them to make them look like this:</div><div><br /></div><div><div>- (IBAction)next:(id)sender;</div><div>- (IBAction)next2:(id)sender;</div><div><br /></div><div>Now we are defining that we are ready to receive two different actions, each taking one argument; the sender of the action.</div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;">WinNavAppDelegate.m</span><br /></div><div><br /></div><div>Since we changed the definition in the h-file we need to do the corresponding change in the m-file, so open up WinNavAppDelegate.m, scroll down to the 'next' and 'next2' methods and edit them to make them look like this:</div><div><br /></div><div><div>- (IBAction)next:(id)sender {</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>NSLog(@"next: sender=%p, button=%p, button2=%p", sender, button, button2);</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>[navigationController pushViewController:viewController2 animated:YES];</div><div>}</div><div><br /></div><div>- (IBAction)next2:(id)sender {</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>NSLog(@"next2: sender=%p, button=%p, button2=%p", sender, button, button2);</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>[navigationController pushViewController:viewController3 animated:YES];</div><div>}</div><div><br /></div><div>Now the method implementation also take one argument, 'sender', and we have also added a call to NSLog() to log some diagnostic output to the console which will make us understand things better. What the NSLog() calls does is to indicate which method that is executing ('next:' and 'next2:' in the beginning of the string), then output the value of the 'sender' argument (a pointer, therefore the %p format specifier) and after that output the value of the 'button' and 'button2' pointers (%p format once again).</div><div><br /></div><div>Build the project (CMD-B) to verify that there are no compilation errors, then double-click on Resources/MainWindow.xib to go back to IB.</div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;">Reconnecting things</span></div><div><br /></div><div>Since we disconnected the 'next' and 'next2' actions during the cleaning up phase in the beginning of this post, we need to reconnect them to make things work again. But hey, why did we disconnect them, if we're now going to reconnect them. Seems kind of stupid, doesn't it? Yes, it does, if it wasn't for the fact that we changed the IBAction defintions in WinNavAppDelegate.h. Therefore, CTRL-drag from "Round Rect Button (Second)" to "Win Nav App Delegate" to connect it to the 'next:' event in the small window that pops up. If you are super-observant you probably noticed the colon (":") at the end of the event name 'next:'. That colon indicates that this event/action takes an argument. That's the 'sender' argument we added in the IBAction definition. By reconnecting the button we tell it to include the 'sender' argument, which it otherwise wouldn't have done and that would have lead to a runtime error since we longer have a zero-argument action defined in our source code. Finish off by reconnecting the "Round Rect Button (Third)" object to the 'next2:' event.</div><div><br /></div><div><span class="Apple-style-span" style="font-size:x-large;">Test run!</span></div><div><br /></div><div>Yes, it's time for a test run again - how fun! Remember to save the MainWindow.xib in IB by pressing CMD-S and then go back to XCode to build and run (CMD-Return). When the simulator is brought to the front, don't do anything before bringing XCode's console window to the front. Do this by selecting the source code window in XCode and press CMD-R. Press the "Second" button to move to the second view controller and then the "Third" button. This produce something like the following in the XCode console window:</div><div><br /></div><div><div>2009-04-28 20:26:35.553 WinNav[13158:20b] next: sender=0x526aa0, button=0x526aa0, button2=0x523630</div><div>2009-04-28 20:26:36.784 WinNav[13158:20b] next2: sender=0x523630, button=0x526aa0, button2=0x523630</div><div><br /></div><div>Notice that the value of the 'sender' argument is different in the two lines? In the first line ('next:') the sender value is equal to the button value and in the second line ('next2:'), the sender value is equal to the button2 value. The values you see here are hexadecimal representations of the pointers for the objects involved in the actions. So how do we interpret this information? It's really pretty logical. In the first line ('next:') the action was sent by the 'button' object which is "Rouded Rect Button (Second)" since we connected it to the 'next' event above. In the second ('next2:') it's the "Rounded Rect Button (Third)".</div><div><br /></div><div>Cool, this means that we can find out which IB created object that sent us an action. If we have connected that IB object to a pointer in our code we can even make intelligent decisions based on the sender of an action since we know exactly which object it was. This probably means that we could have a single IBAction method which handles both of our "next"-buttons. If it is the "(Second)"-button, we push viewController2 and if it is the "(Third)" we push viewController3, but that I leave as an exercise to the reader. But please wait until after the tutorial to test that so we can continue the discussion using a common source code :)</div><div><br /></div><div><span class="Apple-style-span" style="font-size:x-large;">Adding a slider</span></div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;">WinNavAppDelegate.h</span></div><div><br /></div><div>Add the following line to the near end of the file, just above the @end-marker.</div><div><br /></div><div>- (IBAction)slider:(id)sender;<br /></div><div><br /></div><div>That line defines an IBAction with one argument, the sender of the action. Since we're just going to use one slider, you might wonder what we need the sender argument for. This is because we want to be able to read the value of slider, that is, how far to the left or right it has been slided. For that we need the pointer to the UISlider object and that is exactly what we get in the 'sender' argument.</div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;">WinNavAppDelegate.m</span><br /></div><div><br /></div><div><div>Add the following line to the near end of the file, just above the @end-marker.</div><div><br /></div><div>- (IBAction)slider:(id)sender {</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>NSLog(@"slider: sender=%p value=%f", sender, ((UISlider *)sender).value);</div><div>}</div><div><br /></div><div>Here is the implementaiton of the argument. The only thing it does is log some diagnostic information to the console by calling NSLog(). If you check the API docs for UISlider you'll see that it has a property called 'value' which contains the value of the slider between 0.0 and 1.0 (0 to 100%). The "((UISlider *)sender)" magic is standard Objective C syntax for converting an "untyped" pointer into a "typed" pointer. To be able to access the UISlider class' property 'value' we need to work with a UISlider object. The compiler doesn't know that "(id)sender" is a pointer to an UISlider object, so we have to tell it. That's what the magic is used for. Build the project (CMD-R) to save all files and see that there are no compilation errors.</div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;">MainWindow.xib</span></div><div><br /></div><div>Now that we have prepared the source code for adding a slider, we should add the slider in IB as well. Go to IB and double-click on the "View" object under the "View Controller (First)" object. This should bring up the edit window for that view. Drag a slider from the "Inputs & Values" section in the Library window (CMD-L) anywhere onto the view. Check the MainWindow.xib window to verify that the object appeard under the "View" object - it should be called "Horizontal Slider".</div><div><br /></div><div>Now we need to connect the slider to our IBAction. You can do this by CTRL-dragging from "Horizontal Slider" to "Win Nav App Delegate" and connect it to the 'slider:' event. Did you notice the colon which indicates that this event takes one argument (the sender)? However, ther are two other ways of doing this in IB. CTRL-click on "Horizontal Slider" to pop up a window showing all its outlets, etc. Scroll down to the end and you should see a row called "Values changed". At the far end of that row there is a small circle. CTRL-drag this circle to "Win Nav App Delegate" to achieve the same result. Did you notice how the pop up window was made semi transparent in order to "get out of the way"? The third way of doing this is to select "Horizontal Slider" and press CMD-2 to bring up the Connections tab of the Inspector window. Here you'll once again see the "Values changed" row with the circle at the right end and yes you guessed it, this circle can also be CTRL-dragged!</div><div><br /></div><div><span class="Apple-style-span" style="font-size:x-large;">Test run, test run, test run!</span></div><div><br /></div><div>Ok, ok, we'll do a test run again. In fact, this is the final test run since we're done! Remember to save the IB file (CMD-S) and then go to XCode to build and run (CMD-Return). Bring up the XCode console (CMD-R) once the simulator has appeared and then play a little with the slider and you'll see something like this in the console:</div><div><br /></div><div><div>2009-04-28 20:57:46.238 WinNav[13253:20b] slider: sender=0x523620 value=0.500000</div><div>2009-04-28 20:57:46.472 WinNav[13253:20b] slider: sender=0x523620 value=0.489474</div><div>2009-04-28 20:57:46.504 WinNav[13253:20b] slider: sender=0x523620 value=0.478947</div><div>2009-04-28 20:57:46.521 WinNav[13253:20b] slider: sender=0x523620 value=0.468421</div><div>2009-04-28 20:57:46.555 WinNav[13253:20b] slider: sender=0x523620 value=0.457895</div><div>2009-04-28 20:57:46.622 WinNav[13253:20b] slider: sender=0x523620 value=0.447368</div><div>2009-04-28 20:57:46.731 WinNav[13253:20b] slider: sender=0x523620 value=0.447368</div><div><br /></div><div>See how the values decreased? That's because I slided to the left - towards zero. Pretty amazing huh and it wasn't even that hard!</div><div><br /></div></div></div></div></div></div></div></div>henrikbhttp://www.blogger.com/profile/05536290915355204969noreply@blogger.com4tag:blogger.com,1999:blog-3970047569105596105.post-46186952217118080162009-04-27T21:00:00.006+02:002009-04-28T10:12:27.949+02:00iPhone tutorial: Navigation Controller from ScratchIn this post, we're going to test if we really understood what we learned in the previous parts. Those parts focused on analyses, but this time we're going to focus on synthesing, that is creating something of instead of just understanding. We're going to create a navigation controller which handles three custom made view controllers and provide the means to navigate between them. The title says we're going to start from scratch, but we're actually going to start from XCode's "Window-Based Application" template ,which is pretty close to "from scratch".<div><br /></div><div>Start up XCode, choose "File/New Project" from the menu, select "Window-Based Application" and name it "WinNav". After XCode has done it's magic, we have the following project structure.</div><div><br /></div><div>WinNav</div><div> Classes</div><div> WinNavAppDelegate.[hm]</div><div> Resources</div><div> MainWindow.xib</div><div> Info.plist</div><div><br /></div><div>When we're using Interface Builder (IB) to create objects for us, we should usually start by defining Objective C pointers in the source code and then use IB to "connect" the objects to the pointers. When the application is launched, the IB file loading procedure will actually assign values to the pointers.</div><div><br /></div><div><span class="Apple-style-span" style="font-size:x-large;">Classes/WinNavAppDelegate.h</span></div><div><br /></div><div>As was stated initially, we're going use a navigation controller to navigate between three custom view controllers. Furthermore, we're going to add "next"-buttons on the two first view controllers which will take us to the next view controller. Therefore, we start by declaring a navigation controller, three view controllers and two buttons - pretty logical, huh? How do we do this? Simple, open WinNavAppDelegate.h in XCode and add the following lines after the "UIWindow *window" line that came with the template:</div><div><br /></div><div><pre></pre></div><div><div>// DON'T ADD THE FOLLOWING LINE - JUST USED FOR REFERENCE!!!</div><div></div></div><div>UIWindow *window;<br /></div><div><div><div><br /></div></div><div>// navigation controller<br /></div><div>UINavigationController *navigationController;</div><div><br /></div><div>// view controllers</div><div>UIViewController *viewController;</div><div>UIViewController *viewController2;</div><div>UIViewController *viewController3;</div><div><br /></div><div><div>// buttons</div><div>UIButton *button;</div><div>UIButton *button2;<span class="Apple-tab-span" style="white-space:pre"> </span></div><div><br /></div><div><br /></div><div>Since we're going to manipulate all these objects from IB, we have to declare them as properties (to make them accessible to external classes) as well as declare them as IBOutlets so that IB will detect them, when it scans our class defintion files. We do this by adding the following block of code after the 'window' property that came with the template.</div><div><br /></div><div><div><pre></pre></div><div>// DON'T ADD THE FOLLOWING LINE - JUST USED FOR REFERENCE!!!</div><div></div></div><div>@property (nonatomic, retain) IBOutlet UIWindow *window;<br /></div><div><div><br /></div></div><div>// navigation controller<br /></div><div><div>@property (nonatomic, retain) IBOutlet UINavigationController *navigationController;</div><div><br /></div><div>// view controllers</div><div>@property (nonatomic, retain) IBOutlet UIViewController *viewController;</div><div>@property (nonatomic, retain) IBOutlet UIViewController *viewController2;</div><div>@property (nonatomic, retain) IBOutlet UIViewController *viewController3;</div><div><br /></div><div>// buttons</div><div>@property (nonatomic, retain) IBOutlet UIButton *button;</div><div>@property (nonatomic, retain) IBOutlet UIButton *button2;</div><div>- (IBAction)next;</div><div>- (IBAction)next2;</div><div><br /></div><div><br /></div><div>If you've read the previous posts all this should be pretty straight forward to you, but you might have forgotten what the strange IBAction stuff is all about. An action is a way to associate an UI-event (touching a button for example) with an action (a method in a class).</div><div><br /></div><div><div><span class="Apple-style-span" style="font-size:large;">Classes/WinNavAppDelegate.m</span></div><div><br /></div><div>We declared a bunch of properties in the h-file, so the first thing we should do is to add matching @synthesize-instructions to actually create the code required to implement the property specifications.</div><div><br /></div><div><div><div><pre></pre></div><div>// DON'T ADD THE FOLLOWING LINE - JUST USED FOR REFERENCE!!!</div><div></div></div><div>@synthesize window;<br /></div></div><div><br /></div><div><div>// navigation controller</div><div>@synthesize navigationController;</div><div><br /></div><div>// view controllers</div><div>@synthesize viewController;</div><div>@synthesize viewController2;</div><div>@synthesize viewController3;</div><div><br /></div><div>// buttons</div><div>@synthesize button;</div><div>@synthesize button2;</div><div><br /></div><div><br /></div><div>Ok, now that the properties are implemented, we should implement the action methods we declared using the IBAction statements in the h-file. Add the following lines to the end of the m-file, just above the @end-statement.</div><div><br /></div><div><div><pre></pre></div><div>- (IBAction)next {</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>[navigationController pushViewController:viewController2 animated:YES];</div><div>}</div><div><br /></div><div>- (IBAction)next2 {</div><div><span class="Apple-tab-span" style="white-space:pre"> </span>[navigationController pushViewController:viewController3 animated:YES];</div><div>}</div><div><br /></div><div><br /></div><div>The 'next' method will be associate with the "next"-button on the first view controller and the "next2"-button will be associated with the "next"-button on the second view controller. Both these methods do the same thing, they "push" a view controller onto the navigation controller, which automatically (thanks to the navigation controller) will take us to the new view (the one we push). The button on the first view takes us to the second view and the button on the second view takes us to the third. Moving "backwards" is automatically handled by the navigation controller thanks to an automatically added "back"-button in the top left corner.</div><div><br /></div><div>To make all the stuff we have declared visible once we start the application, we need to add the navigation controller's view to our window. We do this by adding a 'addSubView' method call in the 'applicationDidFinishLaunching' that came with the template:</div><div><br /></div><div><div><pre></pre></div><div>// navigation controller</div><div>[window addSubview:[navigationController view]];</div><div><br /></div><div><div>// DON'T ADD THE FOLLOWING LINE - JUST USED FOR REFERENCE!!!</div><div></div></div><div>[window makeKeyAndVisible];</div><div><pre><br /></pre></div><div><br /></div><div><span class="Apple-style-span" style="font-size:x-large;">Resources/MainWindow.xib</span></div><div><br /></div><div>Now that we've prepared our source code for what we want to do, it's time to make an IB file which creates all the objects for us, so double-click on Resources/MainWindow.xib to load the file into IB. Switch to hierarchical view mode in the MainWindow.xib window in IB, by pressing the middle icon just above the "View mode" text in the upper left corner of the window. This should present the contents of the file as:</div><div><br /></div><div>File's Owner [UIApplication]</div><div>First Responder [UIResponder]</div><div>Win Nav App Delegate [WinNavAppDelegate]</div><div>Window [UIWindow]</div><div><br /></div><div>To really see if we understand how to use IB, we'll start by deleting the last two objects by selecting them and press backspace.</div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;">Win Nav App Delegate</span></div><div><br /></div><div>So how do we re-create the "Win Nav App Delegate" object? Choose Tools/Library from the IB menu or press CMD-L. Select "Cocoa Touch Plugin" from the Library window and then "Controllers". Scroll down to the Object-icon and drag it into the MainWindow.xib window. It should be automatically selected so press CMD-4 to bring up the Identity tab of the Inspector window. Change the class to WinNavAppDelegate (our class) and you should see how the list of outlets get populated in the "Class outlets" section; all our buttons and view controllers as well as the window and navigation controller should be present. You should also see our two actions 'next' and 'next2' in the "Class actions" section.</div><div><br /></div><div>Connect the "Win Nav App Delegate" to the 'delegate' property of "File's Owner" by selecting "File's owner" and CTRL-dragging from it to "Win Nav App Delegate". A blue line should appear and when you release the mouse button a window called "Outlets" should appear. Select 'delegate' from it. CTRL-click "Win Nav App Delegate" and then "File's Owner" to verify that the 'delegate' outlet is correctly connected. </div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;">Window</span></div><div><br /></div><div>Let's re-create the "Window" object, by going to the "Windows, Views & Bars" branch in the Library window. Drag the Window icon into the MainWindow.xib window. Connect the Window to the 'window' property of "Win Nav App Delegate" by selecting "Win Nav App Delegate" and CTRL-dragging from it to "Window".</div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;">Navigation Controller</span></div><div><br /></div><div>What's next? Well, we need a navigation controller so select the "Controllers" branch in the Library window again. Drag the "Navigation Controller" into the MainWindow.xib window. See the small arrow to the left of the newly added object? Press it to expand the hierarchy under the controller and you should see a "Navigation Bar" object and a "View Controller (Navigation Item)" object. Yep, there's another small arrow to the left of this object, so press that as well. Look, a "Navigation Item (Navigation Item)" object appeared. IB apparently did a lot of work for us when we drag and dropped the "Navigation Controller" icon. Even if this is a "from scratch" tutorial we're going to accept this help since we'll be adding two more view controllers by ourselves soon. Connect this to the 'navigationController' outlet of "Win Nav App Delegate" by CTRL-dragging from "Win Nav App Delegate" to "Navigation Controller" and select the 'navigationController' outlet.</div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;">Navigation Bar</span></div><div><br /></div><div>Automatically created when we dragged the "Navigation Controller" icon from the Library Window. This is used "internally" by the "Navigation Controller" and shouldn't be manipulated.</div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;">View Controller (Navigation Item)</span></div><div><br /></div><div>Automatically created when we added the "Navigation Controller from the Library. It's a simple UIViewController, but CTRL-click it to see that it's 'navigationItem' property is connected to the "Navigation Item (Navigation Item)" object. This object was placed "under" the "Navigation Bar" object thanks to some special handling in IB - navigation controllers are prepared for it.</div><div><br /></div><div><div><span class="Apple-style-span" style="font-size:large;">Navigation Item (Navigation Item)</span></div><div><br /></div><div>Also automatically created by IB. This object was placed "under" the "View Controller" also thanks to some special handling in IB. It is used to display the title and navigation buttons of the "View Controller". Double-click it to bring the "edit window" for it to the front. Double-click the text "Navigation Item" at the top and change it to "First". Notice how the text "(First)" replaced ("Navigation Item)" in the MainWindow.xib window, both for the "Navigation Item" and the "View Controller".</div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;">A first test run</span></div><div><br /></div><div>Let's take a brief pause and save our IB file (CMD-S), return to XCode to build and run (CMD-Return) and wait for the iPhone simulator to start. You should see a white background with a blue bar at the top saying "First". That's the title of the view controller that was automatically added by IB.</div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;">View Controller (Second)</span></div><div><br /></div><div>To create this object, bring up the Library window in IB (CMD-L) and drag a "View Controller" icon from the "Controllers" section to the end of the list of objects in the MainWindow.xib window and drop it there. We should create a similar hierarchy as for the automatically created view controller so drag the "Navigation Item" icon from the "Windows, Views & Bars" secion in the Library and drop it onto the newly added view controller. It should "swallow" it so that it appears "under" it instead of just "below" it. Double-click the newly added navigation item to bring up the edit window for it and then double-click the "Title" text and change it to "Second".</div><div><br /></div><div>Connect the newly created view controller object, by CTRL-dragging from the "Win Nav App Delegate" object to the "View Controller (Second)" object and choose the 'viewController2' outlet from the menu that pops up.</div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;">View Controller (Third)</span></div><div><br /></div><div>Repeat the steps from above, but name it "Third" instead. Easy, huh?</div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;">Adding views</span></div><div><br /></div><div>View controllers without views are pretty boring, so we'll add a view to each of our controllers. There are two ways of doing this. Either you drag the "View" icon from the "Windows, Views & Bars" section of the Library onto the appropriate "View Controller" object in the MainWindow.xib window or you double-click the appropriate "View Controller" object in the MainWindow.xib window to bring up the edit window and then drag the "View" icon into the edit window (the big area that says "View). Go ahead and try both alternatives.</div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;">Adding buttons</span></div><div><br /></div><div>The navigation controller automatically provides a means for moving "backwards" among its view controllers, just like a "Back" button in a web browser, but it does not provide a "Forward button". Therefore, we have to provide our own. We need one button on the first view controller so we can get to the second and one on the second so we can get to the third.</div><div><br /></div><div>Double-click on the first view controller (the one IB automatically added for us under the navigation controller) to bring up its edit window. As an alternative, you could also double-click on the view controllers view that we added above. Now, drag the "Round Rect Button" icon from the "Inputs & Values" section of the Library window (CMD-L) and drop the icon anywhere in the view controllers edit window that we just brought up. Don't drop it on the blue navigation bar at the top, though. Double-click the newly added button and name it "Second".</div><div><br /></div><div>Connect the newly created button by CTRL-dragging from "Win Nav App Delegate" to the button and select the 'button' outlet from the menu that pops up.</div><div><br /></div><div>Repeat the steps for the second view controller but name the button "Third" and connect it to the 'button2' outlet instead.<br /></div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;">Time for a test run again</span></div><div><br /></div><div>Save the IB-file (CMD-S) and go to XCode to build and run (CMD-R). You should see a white screen with the title "First" in the blue navigation bar at the top and a button titled "Second". That's pretty cool, but hey nothing happens when we press the button! What kind of tutorial is this? </div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;">Bringing the buttons to life</span></div><div><br /></div><div><div>Expand the hierarchies for the first and second "View" objects in the MainWindow.xib window by pressing the small arrow that appeared to the left of them when we added the buttons to them. You should see that a "Rounded Rect Button (Second)" and "Rounded Rect Button (Third)" have been added to the object hierarchy.</div><div><br /></div><div>Select the first button in the MainWindow.xib window in IB and CTRL-drag to "Win Nav App Delegate" button. A small window named "Events" should pop up displaying the two IBActions 'next' and 'next2' we defined in Classes/WinNavAppDelegate.h. Choose 'next'.</div><div><br /></div><div>Repeat the steps for the second button, but choose 'next2' from the list of events instead.</div><div><br /></div><div><div><span class="Apple-style-span" style="font-size:large;">Time for another test run!</span></div><div><br /></div><div><div>Save the IB-file (CMD-S) and go to XCode to build and run (CMD-R). Try to press the "Second"-button to see what happens! Whoa! That's amazing! Look at those smooth movements! Now try pressing the "Third"-button and see how we switch to the third view controller - pretty amazing, right? But that's not all, try pressing the "Second"-button in the upper left corner of the screen - inside the navigation bar. Yeah, that's a "Back"-button which takes us to the previous view controller. So now you can navigate freely between the three view controllers. Not bad, huh? ;)</div><div><br /></div><div><br /></div></div></div></div></div></div></div></div></div></div></div></div>henrikbhttp://www.blogger.com/profile/05536290915355204969noreply@blogger.com31tag:blogger.com,1999:blog-3970047569105596105.post-82141430534570855432009-04-24T20:10:00.005+02:002009-04-26T08:29:59.047+02:00iPhone tutorial: "Navigation-Based Application" part 2<div><div><span class="Apple-style-span" style=" ;font-family:Times;"><div style="margin-top: 8px; margin-right: 8px; margin-bottom: 8px; margin-left: 8px; font: normal normal normal small/normal arial; "><span class="Apple-style-span" style=" ;font-family:Times;font-size:16px;"><div style="border-top-width: 0px; border-right-width: 0px; border-bottom-width: 0px; border-left-width: 0px; border-style: initial; border-color: initial; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 3px; padding-right: 3px; padding-bottom: 3px; padding-left: 3px; width: auto; font: normal normal normal 100%/normal Georgia, serif; text-align: left; "><div><div><div><div><span class="Apple-style-span" style="font-family: Times; font-size: 16px; "><div style="margin-top: 8px; margin-right: 8px; margin-bottom: 8px; margin-left: 8px; font: normal normal normal small/normal arial; "><span style="font-family: Times; font-size: 16px; "><div style="border-top-width: 0px; border-right-width: 0px; border-bottom-width: 0px; border-left-width: 0px; border-style: initial; border-color: initial; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 3px; padding-right: 3px; padding-bottom: 3px; padding-left: 3px; width: auto; text-align: left; "><div><div><div><div style="border-top-width: 0px; border-right-width: 0px; border-bottom-width: 0px; border-left-width: 0px; border-style: initial; border-color: initial; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 3px; padding-right: 3px; padding-bottom: 3px; padding-left: 3px; width: auto; font: normal normal normal 100%/normal Georgia, serif; text-align: left; "><div><div><span class="Apple-style-span" style="font-family: Times; "><div style="margin-top: 8px; margin-right: 8px; margin-bottom: 8px; margin-left: 8px; font: normal normal normal small/normal arial; "><span class="Apple-style-span" style="font-family: Times; font-size: 16px; "><div style="border-top-width: 0px; border-right-width: 0px; border-bottom-width: 0px; border-left-width: 0px; border-style: initial; border-color: initial; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 3px; padding-right: 3px; padding-bottom: 3px; padding-left: 3px; width: auto; font: normal normal normal 100%/normal Georgia, serif; text-align: left; "><div><div>In the last part we mainly analysed the IB files of the "Navigation-Based Application" XCode template. This time we're going to look at the source and also do some modifications to it. This part mainly focuses on the table view (UITableView) so the title of this post could have been "UITableView tutorial".<div><br /></div><div>If you scroll through "Classes/RootViewController.m" in XCode, you'll notice that it mainly consists of methods that are commented out. The reason for why there are so many methods is that this class has a lot of "responsibility". If you double-click on Resources/RootViewController.xib in XCode to load the file into Interface Builder (IB) and then CTRL-click on "File's Owner" in the RootViewController.xib window in IB, you'll see that it has two referencing outlets; 'Table View.dataSource' and 'Table View.delegate', which means that our RootViewController object has been connected to these properties of the "Table View" object in this IB file. The 'tableView' property of our RootViewController has in turn been connected to the "Table View" object. This probably means that we'll receive messages from "Table View"...<br /></div><div><div><br /></div><div>Since "Table View" is of the class UITableView, we should look up that class in XCode's API docs window. Under the "Properties" section in the docs for UITableView we see that the object assigned to the 'dataSource' property should implement the UITableViewDataSource protocol and the object assigned to the 'delegate' property should implement the UITableViewDelegate protocol.</div><div><br /></div><div>That's what we meat by a lot of responsibility above - it is expected to implement these two protocols. The 'dataSource' is intended to supply the actual contents of the table, while the 'delegate' focuses on the presentation of the contents.</div><div><br /></div><div><span class="Apple-style-span" style="font-size: x-large;">UITableViewDataSource</span></div><div><br /></div><div>According to the API docs in XCode this protocol "provides the the table-view object with the information it needs to construct and modify a table view.", but also that it "supplies minimal information about it's appearance".</div><div><br /></div><div>As usual in protocols, a lot of the methods are optional. The only required methods in this protocol are 'cellForRowAtIndexPath' and 'numberOfRowsInSection'. UITableView's definitoin of a table is that it has a number of rows, where each row has a cell which contains the actual contents. Furthermore, a table can be divided into sections, where each section contain a number of rows. This way rows that belong together can be "grouped" and each group (or section) can have its own title, etc.</div><div><br /></div><div>That should explain the "row", "cell" and "section" parts of the method names, but what on earth is an "index path"?<br /></div><div><br /></div><div><span class="Apple-style-span" style="font-size: large;">Index paths</span></div><div><br /></div><div>One way to visualise an index path is to think of a document which is divided into chapters, sections, paragraphs, sentences and words. If you want to "point at" a specific word in the document you could specify that it is in chapter 3, section 2, paragraph 1, sentence 7 and word 5. The "path" to that word would be 3, 2, 1, 7, 5 or perhaps /3/2/1/7/5 to make it look more like the paths used to navigate the file system on a computer or page structure on a web site.</div><div><br /></div><div>If you wanted to present the contents of a book hierarchically using tables you could start by presenting a table listing all the chapters in the book. When the user selects one of the chapters from the table, you switch to a table listing all the sections in that chapter. When the user selects a section, you switch to a table of all the paragraphs in that section, and so on...</div><div><br /></div><div>In this setup you would have a chapter table, section table, paragraph table, sentence table and word table. All tables of a certain kind (chapter, section, etc.) would look alike (visually), but would have different content, since chapter 2 probably doesn't contain the same sections as chapter 1, etc.</div><div><br /></div><div>Thus, index paths can be used to "point at" or reference a specific item in a hierarchy of items or a specific node in a tree if you're familiar with that term. In our context it is used as a reference to a specific row in a hierarchy of tables. If you want to know even more about "index paths" I suggest you do a seach for NSIndexPath in XCode's API docs window since that is the class which implements index paths.</div><div><br /></div><div><span class="Apple-style-span" style="font-size: large;">tableView:numberOfRowsInSection:</span></div><div><br /></div><div>As we said before, a table consists of a number of rows which can be divided into sections ("grouped together"). Even though that it sounds like a "section" is a new level in the hierarchy of tables, that is not the case. Instead, it is a "level" inside the table. Say that we have a table with 5 rows, divided into two sections; the first section has 2 rows and the second has 3:</div><div><br /></div><div>row 0: section 0, row 0</div><div>row 1: section 0, row 1</div><div>row 2: section 1, row 0</div><div>row 3: section 1, row 1</div><div>row 4: section 1, row 2</div><div><br /></div><div>We see that there are two ways to "point to" a specific row in this table; either you specify only the row (0-4) or you speficy the section and row within the section. We also see that it is possible to translate between these two types of "pointers"; row 3 is the same as "section 1, row 1", for example.</div><div><br /></div><div>A more compact way of describing the "structure" of the table above would be</div><div><br /></div><div>section 0: 2 rows</div><div>section 1: 3 rows</div><div><br /></div><div>and that is exactly the method which UITableView uses and the information that the 'numberOfRowsInSection' method should supply. The method gets called with the section (0, 1, ...) as an argument and is expected to return the number of rows for that section.</div><div><br /></div><div>By the way, did you notice anything strange with the title of this section? See that the method name actually starts with "tableView:"? In Objective C, you could say that the "name" of this method actually is "tableView" and that it has an argument called "numberOfRowsInSection". Or you could say it's a method with no name that has two arguments; 'tableView' and 'numberOfRowsInSection'. Ok, let's stop playing around - the name of this method is "tableView:numberOfRowsInSection" and it has two arguments - nothing strange about that! ;)</div><div><br /></div><div>So, what is the 'tableView' argument good for? That is used to identify which tableView that wants to find out how many rows it has. Specifying the tableView makes it possible for a single object to be the data source for multiple (different) tables.</div><div><br /></div><div><span class="Apple-style-span" style="font-size: large;">numberOfSectionsInTableView:</span></div><div><br /></div><div>If you want to create a table with more than one section you have to implement the optional method 'numberOfSectionsInTableView' as well. Otherwise it is assumed that the table only has one section (section 0).</div><div><br /></div><div>Note that this method, just like 'numberOfRowsInSection', specifies the tableView requesting information, but here it is the last argument instead of the first. Don't ask me why.</div><div><br /></div><div><span class="Apple-style-span" style="font-size: large;">tableView:cellForRowAtIndexPath:</span></div><div><br /></div><div>Now that the structure of the table is defined it's time to fill it with some contents. That's when we enter the world of the cells, since it is those that contain the actual contents. A specific cell in a hierarchy of tables is referenced using an "index path", just as we referenced specific words in a book in the explanation of index paths above. An UITableView will ask you to supply a cell for it by giving you an index path - a brutal but quite simple and effiecient way of solving a rather complex problem.</div><div><br /></div><div>This method should return a pointer to an UITableViewCell object, so let's start by looking at the API docs for that class in XCode. Whoa, that's a huge document! I think we'll limit ourselves to the basics in this part of the tutorial.</div><div><br /></div><div><span class="Apple-style-span" style="font-size: large;">initWithFrame:reuseIdentifier:</span></div><div><br /></div><div>It can't get more basic than creating and initialising an object, and initialise is exactly what this method does. If you wonder how to create the object you, as always, call the alloc-method on the class; [UITableViewCell alloc].</div><div><br /></div><div>If you check the XCode API docs for this method, you'll quickly find out that it is possible to call this method with CGRectZero (a "zero sized" rectangle) as the 'Frame' argument and nil as the 'reuseIdentifier' argument. So what are those arguments for if it you more or less can skip them? The API docs go on to say that the 'Frame' needs to be properly specified if you need a complex layout for the cell's contents and 'reuseIdentifier' is used if you want to reuse a cell. Being able to reuse a cell is a performance optimisation and as often is the case, added performance leads to added complexity. This time in the form of a "strange" argument at initialisation. We'll return to this later.</div><div><br /></div><div><span class="Apple-style-span" style="font-size: x-large;">Classes/RootViewController.m</span><br /></div><div><br /></div><div>Finally, we're ready to look at the source!</div><div><br /></div><div><div><span class="Apple-style-span" style="font-family: 'courier new';">- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {</span></div><div><span class="Apple-style-span" style="font-family: 'courier new';"> return 1;</span></div><div><span class="Apple-style-span" style="font-family: 'courier new';">}</span></div><div><br /></div><div><div>Optional method from the UITableViewDataSource protocol. Since it's optional and the default behaviour is to create a table with one section, we could have refrained from implementing this method.</div><div><br /></div></div><div><span class="Apple-style-span" style="font-family: 'courier new';">// Customize the number of rows in the table view.<br /></span></div><div><span class="Apple-style-span" style="font-family: 'courier new';">- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {</span></div><div><span class="Apple-style-span" style="font-family: 'courier new';"> return 0;<br /></span></div><div><span class="Apple-style-span" style="font-family: 'courier new';">}</span></div><div><br /></div><div>Mandatory method from the UITableViewDataSource protocol. This method was thoroughly explained above. Returning 0 sounds a bit boring though, so I suggest you change this to 3 and build and run (CMD-Return). Wow, what a difference! No? You don't see any difference at all? Hmm, strange... But hey, let's try clicking on any of the first three rows in the table in the iPhone simulator. See, they light up in blue. Now try pressing any of the other rows. Nothing happens right? So the value 3 that we returned seems to have accomplished something, right?</div><div><br /></div><div><span class="Apple-style-span" style="font-family: 'courier new';">// Customize the appearance of table view cells.</span></div><div><span class="Apple-style-span" style="font-family: 'courier new';">- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {</span></div><div><br /></div><div>Mandatory method from the UITableViewDataSource protocol.<br /></div><div> </div><div><span class="Apple-style-span" style="font-family: 'courier new';"> static NSString *CellIdentifier = @"Cell";</span></div><div><span class="Apple-style-span" style="font-family: 'courier new';"> </span></div><div><span class="Apple-style-span" style="font-family: 'courier new';"> UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];</span></div><div><br /></div><div>I thought I wouldn't have to explain how to reuse table cells, but such a strange method name as 'dequeueReusableCellWithIdentifier' makes it hard to avoid, so here we go...</div><div><br /></div><div><span class="Apple-style-span" style="font-size: large;">dequeueResuableCellWithIdentifier:</span></div><div><br /></div><div>This method is part of the UITableView and the reason for that is that it is that class that actually "manage" the cells. It justs asks the 'data source' to create the cell for it, but once it is created it is managed by the table view. Remember that we mentioned 'reuseIdentifier' and something about performance optimisations when we discussed how to create and initialise a table cell? Well, this is another part of the optimisation scheme.</div><div><br /></div><div>The scheme basically assumes that most (or many) cells in a table will look alike but contain different contents. For example, all cells (rows) might have a white background, but the text for each cell (row) might be different. So when the table view asks the data source for a cell, the data source can assign an "identifier" (name or "description") for the cell. In our example this identifier could have been the string "cell with white background" or maybe just "white" or why not "cell 1" or just "1". Ok, I guess you get it, it can be named anything really.</div><div><br /></div><div>When the table view gets this cell from the data source it caches it together with its identifier. Later when the data source needs to supply another "white" cell to the table view it can ask the table view if it still has it in its cache and that is exactly what this method does.</div><div><br /></div><div><div><span class="Apple-style-span" style="font-family: 'courier new';"> if (cell == nil) {</span></div><div><span class="Apple-style-span" style="font-family: 'courier new';"> cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];</span></div><div><span class="Apple-style-span" style="font-family: 'courier new';"> }</span></div><div><br /></div><div>If the table view didn't know anything about the cell the data source wants to supply to it, the dequeue-method returns nil and that's why we check for nil here. This also means that we have to create the cell and that's why we call 'initWithFrame' and supply a 'reuseIdentifier'.</div><div> </div><div><span class="Apple-style-span" style="font-family: 'courier new';"> // Set up the cell...</span></div><div><span class="Apple-style-span" style="font-family: 'courier new';"><br /></span></div><div><span class="Apple-style-span" style="font-family: 'courier new';"> return cell;</span></div><div><span class="Apple-style-span" style="font-family: 'courier new';">}</span></div><div><br /></div></div><div>The comment seems to suggest that we should do something with the cell after creating and initialise it. Perhaps because it's a bit boring and not very useful having a table with just "blank" cells in it? Quickly checking the API docs for UITableViewCell in XCode we see that there is a property called 'text', so let's set it by inserting the following right above the return-line:</div><div><br /></div><div><span class="Apple-tab-span" style="white-space:pre"><span class="Apple-style-span" style="font-family: 'courier new';"> </span></span><span class="Apple-style-span" style="font-family: 'courier new';">cell.text = @"My cell";</span></div><div><br /></div><div>Build and run (CMD-Return) and - finally! - you should see somthing else than a boring white background with some thin grey lines on it! More specifically, you should see three rows with the text "My Cell".</div><div><br /></div><div><span class="Apple-style-span" style="font-family: 'courier new';">- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {</span></div><div><br /></div><div>Optional method from the UITableViewDelegate protocol, which, according to the API docs "Tells the delegate that the specified row is now selected". As you can see it references the row by supplying an index path.</div><div><br /></div><div><span class="Apple-style-span" style="font-family: 'courier new';"> // Navigation logic may go here. Create and push another view controller.</span></div><div><span class="Apple-tab-span" style="white-space:pre"><span class="Apple-style-span" style="font-family: 'courier new';"> </span></span><span class="Apple-style-span" style="font-family: 'courier new';">// AnotherViewController *anotherViewController = [[AnotherViewController alloc] initWithNibName:@"AnotherView" bundle:nil];</span></div><div><span class="Apple-tab-span" style="white-space:pre"><span class="Apple-style-span" style="font-family: 'courier new';"> </span></span><span class="Apple-style-span" style="font-family: 'courier new';">// [self.navigationController pushViewController:anotherViewController];</span></div><div><span class="Apple-tab-span" style="white-space:pre"><span class="Apple-style-span" style="font-family: 'courier new';"> </span></span><span class="Apple-style-span" style="font-family: 'courier new';">// [anotherViewController release];</span></div><div><span class="Apple-style-span" style="font-family: 'courier new';">}</span></div><div><br /></div></div><div>Lots of comments that suggest that we should do something here, which is a good idea because otherwise we would just have a "dead" table with almost no interaction. Sure, you would still be able see the contents of the table and actually scroll through it, so a "dead" table isn't completely dead, just a little boring.</div><div><br /></div><div>To make it a little less boring, we'll log something to the console, so insert the following just above the final curly bracket:</div><div><br /></div><div><div><span class="Apple-tab-span" style="white-space:pre"><span class="Apple-style-span" style="font-family: 'courier new';"> </span></span><span class="Apple-style-span" style="font-family: 'courier new';">NSLog(@"row %d was selected", indexPath.row);</span></div><div><br /></div><div>Build and run (CMD-Return) and wait for the table to appear in the iPhone simulator. Once it has, select the source window in XCode and press (CMD-R) to bring the console window to the front. Go back to the simulator and press any of the "My Cell" rows and you should see something like this in the console window:</div><div><br /></div><div><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Courier"><b>2009-04-26 08:17:19.693 Nav1[9992:20b] row 0 was selected</b></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Courier"><b>2009-04-26 08:17:20.868 Nav1[9992:20b] row 1 was selected</b></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Courier"><b>2009-04-26 08:17:21.676 Nav1[9992:20b] row 2 was selected</b></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Courier"><span class="Apple-style-span" style="font-weight: bold;"><br /></span></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Courier"><span class="Apple-style-span" style="font-weight: bold;"><span class="Apple-style-span" style="font-family: Georgia; font-size: 16px; font-weight: normal; "><div><span class="Apple-style-span" style="font-size: x-large;">Summary</span></div><div><br /></div><div>In part one I told you that the table view was a real beast and perhaps you agree by now. Sure, it isn't completely wild and unhandleable, but it sure needs to be tamed before it is of any use to you. We've come some way towards achieving that and now we actually understands what happens in this XCode template project, but I wouldn't be surprised if more will be written on the topic of table views in this blog...</div><div><br /></div></span></span></p></div></div></div></div></div></div></span></div></span></div></div></div></div></div></div></div></span></div></span></div></div></div></div></div></span></div></span></div></div>henrikbhttp://www.blogger.com/profile/05536290915355204969noreply@blogger.com0tag:blogger.com,1999:blog-3970047569105596105.post-68953413352434319402009-04-23T07:53:00.008+02:002009-04-24T14:19:14.370+02:00iPhone tutorial: "Navigation-Based Application"The "Navigation-Based Application" template in XCode introduces us to the "navigation controller" (UINavigationController) and the "table view" (UITableView), which is a real beast, since you can use it for almost everything! Because of that, it is also very popular as you can see by playing around in the standard iPhone applications. If you are unsure where to look, check out "Settings" (the gears-icon on the home screen), since that is the most obvious user of the table view.<div><br /></div><div>Table view is basically used to display a list of items in a table, where each row consists of a cell, which in its turn consists of a couple of "views" filled with content (text, icons, etc.). The table view also offers a "edit mode" which you can use to, for example, edit the contents of the cells or rearrange the rows.</div><div><br /></div><div>This template also introduces a lot of other concepts, so this tutorial will probably be broken into many parts.</div><div><br /></div><div>Enough talk! Start up XCode and choose "File/New Project" from the menu and select the "Navigation-Based Application" template and name the project "Nav1". Expand the Classes and Resources groups in the XCode's "Groups and files" section in the left of XCode's main window that. This shows you that the project has the following structure:</div><div><br /></div><div>Nav1</div><div> Classes</div><div> RootViewController.[hm]</div><div> Nav1AppDelegate.[hm]</div><div> Resources</div><div> RootViewController.xib</div><div> MainWindow.xib</div><div> Info.plist</div><div><br /></div><div>Click on Info.plist to confirm that "MainWindow.xib" is set as the "Main nib base name". After that click on Nav1AppDelegate.h. If you've read the previous posts, you'll see that it starts out in a standard manner; it implements the UIApplicationDelegate protocol and declares a 'window' property (UIWindow). After that a UINavigationController called 'nagivationController' is declared, which we haven't seen before. Both these are defined as properties and marked as IBOutlet so that we can manipulate them from Interface Builder.</div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;">Classes/Nav1AppDelegate.m</span></div><div><br /></div><div>- (void) applicationDidFinishLaunching:(UIApplication *)application {</div><div> [window addSubview:[navigationController view]];</div><div> [window makeKeyAndVisible];</div><div>}</div><div></div><div><br /></div><div>When we implement the 'applicationDidFinishLaunching' method from the UIApplicationDelegate interface we are given a chance to "do stuff" right after the application has been loaded, so that everything will look good when it finally appears on the screen. Here we decide that the view of our UINavigationController should be displayed at startup and therefore we add it as a subview to our window by calling addSubView. We also make our window visible and ensure that it is the "first responder" (of events) by calling makeKeyAndVisible.</div><div><br /></div><div>Ok, now that we have some understanding of what application does at startup, let's take it for a test run by building and running (CMD-Return). Not too exciting, huh? A white screen with a blue bar at the top. But wait, if you look carefully, you'll also notice that there are some thin grey horizontal lines evenly spaced down the white background. Yep, that's a table view with empty cells! The thin lines you see are the row separators.</div><div><br /></div><div><span class="Apple-style-span" style="font-size:x-large;">Resources/MainWindow.xib</span></div><div><br /></div><div>Double-click on the file in XCode to start Interface Builder (IB). As always when working with non-trivial IB-files, it's best to switch view mode to the hierarchical mode (middle button above the text "View Mode" in the upper left corner of the MainWindow.xib window in IB). After doing this, you'll see that the IB-file has the following structure:</div><div><br /></div><div>File's Owner [UIApplication]</div><div>First Responder [UIResponder]</div><div>Nav1 App Delegate [Nav1AppDelegate]</div><div>Window [UIWindow]</div><div>Navigation Controller [UINavigationController]</div><div> Navigation Bar [UINavigationBar]</div><div> Root View Controller [RootViewController]</div><div> Navigation Item [UINavigationItem]</div><div><br /></div><div><div><span class="Apple-style-span" style="font-size:large;">File's Owner [UIApplication]</span></div><div><br /></div><div>CMD-4 shows nothing special. CMD-2 shows us that the 'delegate' property is connected to an object called "Nav1 App Delegate", which is ours.</div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;">First Responder [UIResponder]</span></div><div><br /></div><div>Nothing special.</div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;">Nav1 App Delegate [Nav1AppDelegate]</span></div><div><br /></div><div>CMD-4 shows the class as Nav1AppDelegate which we already knew since we're using the hierarchical view mode in the MainWindow.xib window. Our properties 'window' and 'navigationController' are also shown here. CMD-2 shows us that 'window' is connected to an object called "Window" and 'navigationController' is connected to an object called "Navigation Controller". Both these are explained below. We also see that the "File's owner".delegate property is set to point to this object.</div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;">Window [UIWindow]</span></div><div><br /></div><div>CMD-4 shows it's a plain old UIWindow. CMD-2 shows that the "Nav1 App Delegate".window property is set to point to this object.</div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;">Navigation Controller [UINavigationController]</span><br /></div><div><br /></div><div>CMD-4 shows it's a UINavigationController that has one outlet called 'delegate'. Without knowing the details of how a navigation controller works, we can guess that 'delegate' should point to an object which wants to receive messages from (having it's methods invoked from) this object. CMD-2 shows that this object has four outlets in total, but on CMD-4 we only saw one ('delegate'). This suggests that UINavigationController is a subclass and if you're memory is good you probably recognise the 'navigationItem', 'tabBarItem' and 'view' outlets from an UIViewController.</div><div><br /></div><div>Let's verify this by going back to XCode, select Nav1AppDelegate.h, double-click on UINavigationController to select that word in the source code. Position the mouse over the selected word and CTRL-click and select "Find Selected Text in API Reference". This should bring up a window containing the documenation for the UINavigationController class. There you can see that it inherits from UIViewController : UIResponder : NSObject. Good.</div><div><br /></div><div>CMD-2 also shows that the "Nav1 App Delegate".navigationController property is connected to this object.</div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;">Navigation Bar [UINavigationBar]</span><br /></div><div><br /></div><div>CMD-4 shows that this is a UINavigationBar with a 'delegate' outlet. CMD-2 shows that none of its outlets are connected.</div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;">Root View Controller [RootViewController]</span></div><div><br /></div><div>CMD-4 shows the class is RootViewController, which is one of our classes, and has no outlets (of its own). CMD-2 however shows the standard UIViewController outlets so this class apparently inherits from that class. The 'navigationItem' outlet is connected to an object called "Navigation Item". If we search for 'navigationItem' in the XCode API docs window which we popped up a while ago and click on the search result which represents the property of that name, we read that the navigation item referred by the property is used to [visually] represent the view controller when it is "pushed onto a navigation bar". Is it some kind of icon or what? Read on and you'll find out.</div><div><br /></div><div>CMD-1 normally doesn't show anything exciting, but here it does. It shows that this object will be loaded from an IB-file called "RootViewController", which exists in the Resources group of our XCode project. If you've read the previous posts, you are familiar with the concept of a "placeholder" (proxy) object like this one.</div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;">Navigation Item [UINavigationItem]</span></div><div><br /></div></div><div>CMD-4 shows the class as UINavigationItem and a rather long list of outlets; 'backBarButtonItem', 'leftBarButtonItem', 'rightBarButtonItem' and 'titleView'. CMD-2 shows that none of these properties are connected to anything. Maybe that's why we saw no buttons and no title when we test ran the application from XCode in the iPhone simulator? Sounds reasonable, but to really understand what these outlets are for I suggest you search for 'UINavigationItem' in the API docs window in XCode.</div><div><br /></div><div>From the API doc we can read that a navigation bar (the blue bar at the top on the screen you saw when you test ran the application) normally displays a "Back"-button to the left and a title in the center. The "Back"-button is normally labelled with title of the "back" (previous) screen, but you can override this by setting the 'backBarButtonItem' property. The docs also indicates that if you accept the standard behaviour, you won't need to set any of the properties. Good, that means that we can leave them for now...</div><div><br /></div><div>Or wait, not just yet! If you press CMD-1, you will see that there are some edit boxes for "Title" "Prompt" and "Back Button" which lets you set the text for those properties. This means, that if you are satisfied with just having a custom text - compared with having custom buttons - you can enter the text here. Try entering "MyTitle", "MyPrompt" and "MyBack" in the boxes and then take a look at the MainWindow.xib window. See that a small arrow appeared to the left of "Navigation Item"? Press it to expand the hierarchy and you'll see that a "Bar Button Item (MyBack)" object was added. If you're really observant you'll also see that "(MyTitle)" was added on the row of the "Navigation Item". Here IB offers a really simple way of customising the navigation controller - it even creates the necessary "Bar Button Item" object for you.</div><div><br /></div><div>The "MyPrompt" text we entered isn't visible in the MainWindow.xib window, but if you double-click on the "Navigation Item (MyTitle)" row in the window, the "edit window" will pop up and you'll be able to see the navigation controller we are designing. There, at the top of the screen, you'll see the "MyPrompt" text as well. That text is actually assigned to the 'prompt' property. After seeing this you should delete the text though, because setting it seems to trigger a bug in XCode which makes it impossible to build the application.</div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;">Bar Button Item (MyBack) [UIBarButtonItem]</span></div><div><br /></div><div>CMD-4 shows that the class is UIBarButtonItem. CMD-2 shows that there are no outlets, but it also shows a section called "Sent Actions" which we aren't used to seeing here. We might return to that later.</div><div><br /></div><div>Ok, save the changes in IB (CMD-S) and go back to XCode and build and run (CMD-R). You did remember to clear the "Prompt" field of the "Navigation Item" right? Otherwise it won't build. Now the application should look a bit more exciting since it displays "MyTitle" in the blue navigation bar at the top of the screen.</div><div><br /></div><div><span class="Apple-style-span" style="font-size:x-large;">Resources/RootViewController.xib</span></div><div><br /></div><div>This is quite small IB file which has the following structure.</div><div><br /></div><div>File's Owner</div><div>First Responder</div><div>Table View</div><div><br /></div><div>Remember that this file is intended to be "loaded into" one of the objects in MainWindow.xib? This was configured by setting the "NIB name" attribute (CMD-1) of "Root View Controller" to "RootViewController". This means that "Root View Controller" will "own" this file, which brings us to...</div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;">File's Owner [RootViewController]</span></div><div><br /></div><div>CMD-4 shows that the class of this object is our class RootViewController - that is an object of that class will "own" this file. It also shows that it has no outlets of its own, but if we take a brief look at RootViewController.h in XCode we can see that it is a subclass of UITableViewController, which in turn is a subclass of UIViewController. This means that at least the standard view controller outlets are available.</div><div><br /></div><div>CMD-2 indeed shows that there are quite a lot of outlets; 'navigationItem', 'tabBarItem', 'tableView' and 'view'. We have seen three of these before, but 'tableView' is new and comes from UITableViewController. An interesting thing here is that both 'tableView' and 'view' are connected to the same object - "Table View". This makes sense since "Table View" is of the class UITableView which inherits from UIView, so 'view' is connected to the "UIView part" of "Table View".</div><div><br /></div><div>CMD-2 also shows that there are two referencing outlets, that is, properties of other objects that are connected to this object; "Table View".dataSource and "Table View".delegate to be precise.</div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;">First Responder [UIResponder]</span></div><div><br /></div><div>As always(!) quite uninteresting...</div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;">Table View [UITableView]</span></div><div><br /></div><div>CMD-4 shows it is of the class UITableView and has a 'dataSource' outlet. A search for UITableView in the XCode API docs window brings up the docs for that class and after expanding the Properties section in the left frame we can click on 'dataSource' and read that it should be set to an object which implements the UITableViewDataSource protocol. Now that we know that we can move on.</div><div><br /></div><div>CMD-2 shows two outlets; 'dataSource' and 'delegate', but where did 'delegate' come from since we didn't see it under the Identity tab (CMD-4)? Yet another look in the XCode API docs window for UITableView shows that it is a subclass of UIScrollView and that's where the 'delegate' property comes from. It also shows that the protocol implemented by 'delegate' should be UIScrollViewDelegate.</div><div><br /></div><div>Both these properties are connected to "File's owner" which is the RootViewController in MainWindow.xib. This means that that object will receive messages from both the UITableView and the UIScrollView part.</div><div><br /></div><div>Finally, we see that there are two referencing outlets. Once again that means that some other object's outlets have been connected to this object. Here it is "File's Owner".tableView and "File's Owner".view that has been connected to this object.</div><div><br /></div><div><span class="Apple-style-span" style="font-size:x-large;">Time for a break</span></div><div><br /></div><div>Phew! I initially told you that this template would introduce a lot of new concepts and it sure did! All this text and so far we have only analysed the IB files and a little of the source code and still we only have an empty table! At least I need a break now, so the rest of the analysis and some more interesting stuff will follow in part two.</div><div><br /></div><div><br /></div>henrikbhttp://www.blogger.com/profile/05536290915355204969noreply@blogger.com1tag:blogger.com,1999:blog-3970047569105596105.post-33388600065462778672009-04-20T22:20:00.005+02:002009-04-20T22:53:52.536+02:00iPhone tutorial: "Tab Bar Application" part 2In the last post I mentioned that I would probably write a short post commenting on the source of the XCode "Tab Bar Application" template. There really isn't that much to say about the source since there is very little of it - almost everything we see when running the app happens because of the objects in the xib files.<div><br /></div><div>Let's take a look at one thing though. Go to XCode and select Classes/Tab1AppDelegate.m. Scroll down to the method 'didSelectViewController' and un-comment it. Edit the source so that the body of the method looks like this:</div><div><br /></div><div><br /></div><div><pre></div><div><span class="Apple-tab-span" style="white-space:pre"> </span>NSLog([NSString stringWithFormat:@"didSelectViewController:%@", viewController.title]);<br /></div><div></pre></div><div></div><div><br /></div><div>Run the program by pressing CMD-Return and after the iPhone simulator window appears, select the XCode window and press CMD-R to bring the debugger window to the front. We do this, since this is where the text produced by NSLog() appears.</div><div><br /></div><div>didSelectViewController is part of the UITabBarControllerDelegate protocol so we can expect the tab bar to "send this message" to us, that is, call this method when we press one of the buttons on the tab bar. Let's try that. Yes, go ahead and press the tab bar buttons in the simulator and see what happens. Did you remember to press CMD-R in XCode to bring the debugger window to the front by the way? If not, do it now.</div><div><br /></div><div>Ok, so what happened? Not much, huh? Have we found a bug in the iPhone API or why isn't the method called? No, it's not a bug in the iPhone API but maybe in the XCode template application. Or more specifically in MainWindow.xib. To resolve the problem/bug, double-click on Resources/MainWindow.xib to start Interface Builder (IB). In IB go to the MainWindow.xib file and select "Tab Bar Controller" and press CMD-2. See that it has a 'delegate' outlet which isn't connected to anything? That's probably the explanation to the problem. Let's connect it to "Tab1 App Delegate" and see if that works better.</div><div><br /></div><div>Remember that you have to "think backwards" (atleast in my view) to connect an outlet by CTRL-dragging in IB? Ok, start by positioning the mouse over "Tab Bar Controller". After that press and hold CTRL while you drag the mouse until it's positioned over "Tab1 App Delegate". Now release the mouse and a window should pop up. In that window, click on 'delegate'. This should have connected the 'delegate' outlet of "Tab Bar Controller". To verify this, select "Tab Bar Controller" and press CMD-2. A connection now exists to "Tab1 App Delegate", right? If not, try again! When you have succeeded, save the IB file (CMD-S).</div><div><br /></div><div>Return to XCode and build and run (CMD-Return). Wait for iPhone Sim to start up and then select the source window in XCode again and bring up the debugger window (CMD-R). Play around in the simulator a bit by pressing the two buttons and you should see something like this in the debugger window:</div><div><br /></div><div><pre></pre></div><div><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Courier"><b>2009-04-20 22:40:14.842 Tab2[5188:20b] didSelectViewController:First 2</b></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Courier"><b>2009-04-20 22:40:19.972 Tab2[5188:20b] didSelectViewController:(null)</b></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Courier"><b>2009-04-20 22:40:22.026 Tab2[5188:20b] didSelectViewController:First 2</b></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Courier"><b>2009-04-20 22:40:23.227 Tab2[5188:20b] didSelectViewController:(null)</b></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Courier"><b>2009-04-20 22:40:24.506 Tab2[5188:20b] didSelectViewController:First 2</b></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Courier"><b>2009-04-20 22:40:25.746 Tab2[5188:20b] didSelectViewController:(null)</b></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Courier"><b>2009-04-20 22:40:27.139 Tab2[5188:20b] didSelectViewController:First 2</b></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Courier"><span class="Apple-style-span" style="font-weight: bold;"></span></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Courier"><span class="Apple-style-span" style="font-weight: bold;"><br /></span></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Courier"><span class="Apple-style-span" style="font-weight: bold;"><span class="Apple-style-span" style=" font-weight: normal; font-family:Georgia;font-size:16px;"><div>Wow, it works! But why do we get a "(null)" title for one of the view controllers? The second one to be specific. Go back to IB and select "First View Controller" in MainWindow.xib and press CMD-1. See that the 'title' is set to "First 2"? That's because it was set to that name in the "View Controllers" table in the "Tab Bar Controller" object in XCode (select it and press CMD-1 to verify). However, if you select the second view controller in MainWindow.xib - the one called "View Controller (Second)" and press CMD-1, you'll see that the title here is empty even though it had a name in the view controllers table in "Tab Bar Controller".</div><div><br /></div><div>This probably has to do with the fact that the second view controller is defined in and loaded from another xib-file - Resources/SecondView.xib - but I'm not completely sure. To remedy this and set the title, select "View Controller (Second)", press CMD-1 and edit the title. Save the xib file (CMD-1) and return to XCode and build and run (CMD-R) and you'll see that it works.</div><div><br /></div><div><br /></div></span></span></p></div>henrikbhttp://www.blogger.com/profile/05536290915355204969noreply@blogger.com2tag:blogger.com,1999:blog-3970047569105596105.post-37296862640631094102009-04-19T08:08:00.007+02:002009-04-19T16:40:04.402+02:00iPhone tutorial: Analysing XCode's "Tab Bar Application" templateIf you've read the previous posts you probably know where to start, but here comes a brief procedure: Start XCode, choose "File/New project" from the menu, select the "Tab Bar Application" icon and name the project "Tab1". After XCode has created the project for you, expand the "Classes" and "Reosources" groups in XCode's "Groups and files" frame, you'll see that this template contains the following:<div><br /></div><div>Tab1</div><div> Classes</div><div> FirstViewController.[hm]</div><div> Tab1AppDelegate.[hm]</div><div> Resources</div><div> SecondView.xib</div><div> MainWindow.xib</div><div> Info.plist</div><div><br /></div><div>To get a quick overview of what the application does, build and run it in XCode by pressing CMD-Return. The iPhone simulator should pop up and you should see a white background with the text "First View" on it. Below that is a block of text and at the bottom of the screen there are two grey-blackish buttons labeled "First" and "Second". If you press the button called "Second" you are presented with another white background, but this time with the text "Second View" on it. It also has a block of text and the two buttons labeled "First" and "Second" at the bottom.</div><div><br /></div><div>The buttons at the bottom of the two screens are implemeted by using a "tab bar controller" (UITabBarController) which is used to easily switch between a number of full screen views. In this context, full screen means the area above the buttons at the bottom, because the tab bar has reserved the bottom part of the screen for itself. If it didn't do this, it would be pretty hard to switch to another view since its button/tab would be obscured by the contents of the view.</div><div><br /></div><div>After familiarising ourselves with the application we can do the following guesses about the files in project: Tab1AppDelegate.[hm] is the class implementing the UIApplicationDelegate protocol, FirstViewController.[hm] is probably used for implementing the view which had the text "First View" written all over it, MainWindow.xib is the IB file loaded automatically at start up (double check the "Main nib file base name" property in Info.plist to verify this) and SecondView.xib is the IB file describing how the second view should look like.</div><div><br /></div><div>All this sounds logical, but some things seem a bit odd. Why isn't there a FirstView.xib file and no SecondViewController.[hm] files? We'll have to start digging to understand that and let's start with the h-files. </div><div><br /></div><div><span class="Apple-style-span" style="font-size:x-large;">Classes/Tab1AppDelegate.h</span></div><div><br /></div><div><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco"><span style="color:#aa0d91;">@interface</span> Tab2AppDelegate : NSObject <uiapplicationdelegate,> {</uiapplicationdelegate,></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco"><br /></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco"><span class="Apple-style-span" style=" ;font-family:Georgia;"><span class="Apple-style-span" style="font-size:medium;">This class implements two protocols, the usual UIApplicationDelegate and a new acquaintance called UITabBarControllerDelegate. An educated guess whould be that the new protocol is used for "receiving messages" from a UITabBarController.</span></span><br /></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; color:#5c2699;"><span style="color:#000000;"> </span>UIWindow<span style="color:#000000;"> *</span><span style="color:#3f6e74;">window</span><span style="color:#000000;">;</span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; color:#5c2699;"><span style="color:#000000;"> </span>UITabBarController<span style="color:#000000;"> *</span><span style="color:#3f6e74;">tabBarController</span><span style="color:#000000;">;</span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco">}</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; color:#aa0d91;">@property<span style="color:#000000;"> (</span>nonatomic<span style="color:#000000;">, </span>retain<span style="color:#000000;">) </span>IBOutlet<span style="color:#000000;"> </span><span style="color:#5c2699;">UIWindow</span><span style="color:#000000;"> *</span><span style="color:#3f6e74;">window</span><span style="color:#000000;">;</span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; color:#aa0d91;">@property<span style="color:#000000;"> (</span>nonatomic<span style="color:#000000;">, </span>retain<span style="color:#000000;">) </span>IBOutlet<span style="color:#000000;"> </span><span style="color:#5c2699;">UITabBarController</span><span style="color:#000000;"> *</span><span style="color:#3f6e74;">tabBarController</span><span style="color:#000000;">;</span></p><div><br /></div><div><span class="Apple-style-span" style=" ;font-family:Monaco;"><span class="Apple-style-span" style=" ;font-family:Georgia;"><span class="Apple-style-span" style="font-size:medium;">We have pointers to an UIWindow and an UITabBarController which we want to be externally accessible as well as being manipulated by Interface Builder (IB). Therefore we declare the pointers as properties as well as mark them with IBOutlet.</span></span><span class="Apple-style-span" style="font-size:medium;"><br /></span></span></div><div><br /></div><div><span class="Apple-style-span" style="font-size:x-large;">Classes/FirstViewController.h</span><br /></div><div><br /></div><div>Nothing exciting, it just declares that it is a subclass of UIViewController.</div><div><br /></div><div><div><span class="Apple-style-span" style=" "><span class="Apple-style-span" style="font-size:x-large;">Resources/MainWindow.xib</span></span><br /></div><div><br /></div><div>Double-click it to bring up IB and immediately switch view mode in the MainWindow.xib window which appears in IB by selecting the middle button above the "View mode" text in the upper left corner of the window. This is the hierarchical view which you can explore by pressing the small arrows to the left of the objects to see the full hierarchy of objects in this xib-file. Press all the arrows you can find to fully expand the list into this:</div><div><br /></div><div>File's owner [UIApplication]</div><div>First Responder [UIResponder]</div><div>Tab1 App Delegate [Tab1AppDelegate]</div><div>Window [UIWindow]</div><div>Tab Bar Controller [UITabBarController]</div><div> Tab Bar [UITabBar]</div><div> Selected First View Controller [FirstViewController]</div><div> View [UIView]</div><div> Label (First View) [UILabel]</div><div> Text View [UITextView]</div><div> Tab Bar Item (First) [UITabBarItem]</div><div> View Controller (Second) [UIViewController]</div><div> Tab Bar Item (Second) [UITabBarItem]</div><div><br /></div><div>This view mode in IB is so much better than the default which only shows the top level objects as big icons! In this view you can see all the objects in the file and even their classes, which is great for quickly understanding what we're working with. If this view also had shown all the connections between the objects we would have known almost everything about the file...</div><div><br /></div><div>Hmm, there actually is a way to see the connections. Not all at once which would have been really neat, but you can at least see them. Position the mouse pointer over the object (row) in the table you want to examine and CTRL-click a window will pop up which shows all the connections (outlets and referencing outlets). You have to close this window manually so if you repeat the procedure for all objects you can actually see all the connections at once, even though there will be a lot of windows on your screen! ;)</div><div><br /></div><div>Before starting to dig, let's play a little! You probably noticed that IB opened another window besides the MainWindow.xib window, namely a big iPhone-sized window displaying the text "First View" in a quite large font. Yep, that's the contents of this xib displayed graphically, that is, how the objects will be layed out (positioned) in the iPhone when we run the application. This is not a full simulation of the interface, but rather an "edit view" of the interface where we can manipulate the objects instead of just interact with them. Interaction is possible here too though as you can experience by pressing the "Second" button at the bottom of the window.</div><div><br /></div><div>Something happened, right? But maybe not exactly what you expected? Instead of seeing a big bold text saying "Second View" as we did when we test ran the application in the simulator from XCode we see a rather modest text saying: View. Loaded from "SecondView.nib". When you pressed the "Second button" something less obvious also happened. What I'm talking about becomes visible if you activate the MainWindow.xib window. At the end of the list of objects in this window is the object "Selected View Controller (Second)".</div><div><br /></div><div>Really observant readers might remember that in the "full hierarchy" list above there was no "Selected" text before this object's name. However, there was a "Selcted" before the "First View Controller (First)" object's name, which it no longer is. This "Selected" text thus indicates which view controller we currently have selected in the edit window. Press the "First" button and after that activate the MainWindow.xib window and you'll see that the "Selcted" text has been moved back to its original position. (The fact that you have to activate the MainWindow.xib window to see this change seems like a bug in IB.)</div><div><br /></div><div>Speaking of the MainWindow.xib window. As you can see, some object names also contains some text within parentheses, like "First View Controller (First)" and "Label (First View)". The text within parentheses represent the "identifying" value of an object. A label is used to display a short text and thus the "text" property is shown. You can verify this by double clicking on the "First View" text in the edit window and change the text. After pressing return, the new text will be visible within parentheses in the MainWindow.xib window.</div><div><br /></div><div>So where does the text "(First)" in "First View Controller (First)" come from and which property of a view controller does IB consider to be the "identifying" since it is displaying it here? That we'll find out when we start examining all the objects in detail, which we'll do right now!</div><div><br /></div><div><span class="Apple-style-span" style="font-size:x-large;">Resources/MainWindow.xib</span></div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;">File's owner</span></div><div><br /></div><div>CMD-4 shows us that the class is UIApplication (which we already knew since we're using the hierarchical view mode in the MainWindow.xib window) and CMD-2 shows us that the 'delegate' property is set to 'Tab1 App Delegate' (which we also can see by CTRL-clicking on the object in the MainWindow.xib window). This seems logicaly since we in the beginning of this post saw that Tab1AppDelegate.h declared that Tab1AppDelegate.m implements the UIApplicationDelegate protocol.</div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;">First Responder</span></div><div><br /></div><div>Nothing exciting.</div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;">Tab1 App Delegate</span><br /></div><div><br /></div><div>CMD-4 gives us the class "Tab1AppDelegate" and the outlets 'tabBarController' of class UITabBarController and 'window' of class UIWindow just like we declared in Tab1AppDelegate.h. CMD-2 shows that 'tabBarController' is connected to an object named "Tab Bar Controller" and 'window' is connected to an object named "Window", both of which you can see in the hierarchical view of the MainWindow.xib window.</div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;">Window</span></div><div><br /></div><div>CMD-4 says it's an UIWindow (surprise!) and CMD-2 says that the 'window' property of the "Tab1 App Delegate" object has been connected to this window.</div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;">Tab Bar Controller</span></div><div><br /></div><div>CMD-4 says it's a UITabBarController and that it has an outlet called 'delegate', which probably means that this object is going to send messages to (call methods of) another object. Guessing some more, the receiving object should probably implement the UITabBarControllerDelegate protocol, just like we saw in Tab1AppDelegate.h. CMD-2 however shows that the 'delegate' outlet is unconnected - how disappointing! We'll have to examine that further in a while...</div><div><br /></div><div>CMD-1 (attributes) shows the most exciting part of this object - a table called "View Controllers" with two columns "Title" and "Class". A tab bar controller controls a set of view controllers and this table specifies which objects that represent the view controllers.</div><div><br /></div><div>If you want to read more about this right now I suggest that you check out the API docs for UITabBarController. This is easiest done by going to XCode, select the file Tab1AppDelegate.h, select the text UITabBarController in the interface definition and CTRL-click on the selected text. This should pop up a window where you can select "Find Selected Text in API Reference". This should bring up a new window with some search result in the upper half and text in the lower half. To the left there is a table of contents section where you should find the section "Properties". Expand that and you should see a property called 'viewControllers' - that's the property we are manipulating in the table in IB.</div><div><br /></div><div>Ok, back to the "View Controllers" table in IB. Double-click on the Title-column in the first table row, right where it says "First". Edit the text to say "First 2" or something similar. Notice how the "(First)" text was changed to "(First 2)" in the MainWindow.xib window for the "First View Controller"? So this is where the "(First)" text came from - it's the view controller's title. If you return to the edit window, you'll see that this is the text presented on the button at the bottom of the screen, which selects this view controller.</div><div><br /></div><div>Now try pressing the plus-sign ('+') just below the table. A new row should appear in the table and if you check the MainWindow.xib window you should notice that a new view controller and associated "Tab Bar Item" should have appeared at the end of the list. Then check the edit window to see that a new button also should have appeared. This is how easy it is to add a view controller to a tab bar controller. Remove the third controller we just created by selecting it in the table and pressing the minus-sign ('-') under the table or press backspace.</div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;">Tab Bar</span></div><div><br /></div><div>This is more or less an "internal" object of the tab bar controller which we shouldn't mess with in IB, or anywhere really. This is what the API docs for UITabBarController says about it: </div><div><br /></div><div><span class="Apple-style-span" style="font-size:medium;">"</span><span class="Apple-style-span" style=" ;font-family:'Lucida Grande';"><span class="Apple-style-span" style="font-size:medium;">Although a tab bar controller uses a tab bar in its implementation, you should never need to, nor should you, access the tab bar directly."</span></span></div><div><span class="Apple-style-span" style="font-family: 'Lucida Grande';"><br /></span></div><div><span class="Apple-style-span" style=" ;font-family:'Lucida Grande';font-size:12px;"><span class="Apple-style-span" style=" ;font-family:Georgia;font-size:16px;"><div><span class="Apple-style-span" style="font-size: large;">First View Controller (First 2)</span><span class="Apple-style-span" style="font-size: medium;"><span class="Apple-style-span" style=" "></span></span></div><div><span class="Apple-style-span" style="font-size:large;"><span class="Apple-style-span" style="font-size: medium;">CMD-4 says it is of the class "FirstViewController", which is one of our classes - go check in XCode if you had forgotten about it. No outlets are shown here, but since it's a subclass of</span> <span class="Apple-style-span" style=" "><span class="Apple-style-span" style="font-size: medium;">UIViewController (check the h-file in XCode) it of course has the standard outlets of a UIViewController. This can be seen in CMD-2 where we see that one of the standard outlets, 'tabBarController' is connected to an object called "Tab Bar Item (First)" and 'view' is connected to an object called "View". Both these are explained below, while the 'tabBarItem' is best understood by reading the API docs for UIViewController, especially the section about the 'tabBarItem' property. Basically, this property is used to defined how this view controller should be represented on a tab bar, that is what its "button" will look like.</span></span></span></div><div><span class="Apple-style-span" style="font-size: medium;"><br /></span></div><div><span class="Apple-style-span" style="font-size: medium;">CMD-1 gives the title as "First 2" - hey, that's what we changed it to in the "View Controllers" table in the "Tab Bar Controller" object a short while ago. The "NIB Name" is empty, but we'll soon see an example where it isn't - exciting, huh?</span></div><div><span class="Apple-style-span" style="font-size:medium;"><br /></span></div><div><div><span class="Apple-style-span" style="font-size: large;">View</span></div><div><span class="Apple-style-span" style="font-size:medium;"><br /></span></div><div><span class="Apple-style-span" style="font-size: medium;">Neither CMD-4 or CMD-2 show anything surprising - it's a simple UIView.</span></div><div><span class="Apple-style-span" style="font-size:medium;"><br /></span></div><div><div><span class="Apple-style-span" style="font-size: large;">Label (First View)</span></div><div><span class="Apple-style-span" style="font-size:medium;"><br /></span></div><div><span class="Apple-style-span" style="font-size: medium;">Neither CMD-4 or CMD-2 show anything surprising - it's a simple UILabel. CMD-1 on the other hand shows that the text-property is set to "First View" which is exactly the same as the text we saw when we test ran the application in XCode or look at the edit window here in IB. It's the text that the label should display - as simple as that!</span></div><div><span class="Apple-style-span" style="font-size:medium;"><br /></span></div><div><div><span class="Apple-style-span" style="font-size: large;">Text View</span></div><div><span class="Apple-style-span" style="font-size:medium;"><br /></span></div><div><span class="Apple-style-span" style="font-size: medium;">Neither CMD-4 or CMD-2 show anything surprising - it's a simple UITextView. A bit more surprising is that CMD-1 doesn't show anything special either, so how did the text get into the view? Well, it's as simple as double-clicking the text view in the edit window and type what you want it to display. IB ensures that the text finally ends up in the UITextView's text-property.</span></div><div><span class="Apple-style-span" style="font-size:medium;"><br /></span></div><div><div><span class="Apple-style-span" style=" "><span class="Apple-style-span" style="font-size: large;">Tab Bar Item (First 2)</span></span></div><div><span class="Apple-style-span" style="font-size:medium;"><br /></span></div><div><span class="Apple-style-span" style="font-size: medium;">CMD-4 and CMD-2 are both unexciting. CMD-1 says that the title is 'First 2' - here we go again. It seems as if the text we entered in the "View Controllers" table of the tab bar controller affected a couple of objects - first the view controller's title and now this. Read more about tab bar items by searching for UITabBarItem in the API docs in XCode.</span></div><div><span class="Apple-style-span" style="font-size:medium;"><br /></span></div><div><div><span class="Apple-style-span" style=" "><span class="Apple-style-span" style="font-size: large;">View Controller (Second)</span></span></div><div><span class="Apple-style-span" style="font-size:medium;"><br /></span></div><div><span class="Apple-style-span" style="font-size: medium;">Ok, now the tricky part of this tutorial starts so keep focused. Remebered that the "second view" looked a bit strange when we switched to it in the edit window in IB? It said something about that this view was loaded from a nib. Double click on the "View Controller (Second)" row in the MainWindow.xib window and we'll see exactly what it said since this pops up the edit window. There we go: Loaded from "SecondView.nib".</span></div><div><span class="Apple-style-span" style="font-size: medium;"><br /></span></div><div><span class="Apple-style-span" style="font-size: medium;">If you take a look in the MainWindow.xib window you'll see another strange thing. The branch of the object hierarchy starting with "First View Controller" has lots of objects under it - View, Label, TextView, TabBarItem - while the branch starting at "Second View Controller" just has a TabBarItem. This is strange since the first and second views looked more or less identical - both had a title and some text. It turns out the View, Label and TextView items for this view are loaded from a another IB-file, "SecondView.xib" to be specific.</span></div><div><span class="Apple-style-span" style="font-size: medium;"><br /></span></div><div><span class="Apple-style-span" style="font-size: medium;">This is pretty cool. Instead of defining "everything" in a single IB file you can modularise your interface design into several IB files and then refer to them from a main IB file or similar. For a tab bar controller interface, this is very nice.</span></div><div><span class="Apple-style-span" style="font-size: medium;"><br /></span></div><div><span class="Apple-style-span" style="font-size: medium;">So how does this magic happen? Lets explore the object and see if we can find out. CMD-4 shows nothing special - it's an UIViewController with the standard outlets. CMD-2 shows that one of its outlets, 'tabBarItem', is connected to an object called "Tab Bar Item (Second)". It's also worth noting that the 'view' outlet isn't connected to anything. This is different from how it was in "First View Controller" (see above) and will therefore be explained shortly.</span></div><div><span class="Apple-style-span" style="font-size:medium;"><br /></span></div><div><div><span class="Apple-style-span" style=" "><span class="Apple-style-span" style="font-size: large;">Tab Bar Item (Second)</span></span></div><div><span class="Apple-style-span" style="font-size:medium;"><br /></span></div><div><span class="Apple-style-span" style="font-size: medium;">This is the tab bar item for the second view controller. It is defined in the same way as the item for the first view controller. It's worth noting that it is defined here in MainWindow.xib and not in SecondView.xib though. The reason for that is that it "belongs to" the tab bar controller and tab bar which are defined in this IB file.</span></div><div><span class="Apple-style-span" style="font-size:medium;"><br /></span></div><div><span class="Apple-style-span" style=" "><span class="Apple-style-span" style="font-size: x-large;">Resources/SecondView.xib</span></span><span class="Apple-style-span" style="font-size:medium;"><br /></span></div><div><span class="Apple-style-span" style="font-size:medium;"><br /></span></div><div><span class="Apple-style-span" style="font-size:24px;"><span class="Apple-style-span" style=" ;font-size:16px;"><div><span class="Apple-style-span" style="font-size: large;">File's owner</span></div><div><span class="Apple-style-span" style="font-size:medium;"><br /></span></div><div><span class="Apple-style-span" style="font-size: medium;">You have remembered to switch the SecondView.xib window view mode to the hierarchical view mode (middle button above the text "View Mode"), right? Good, this should be made into a habit. All the "File's owner" object we have examined so far have been pretty boring, but this one actually is a bit interesting. Why? Press CMD-4 and you'll see that the class is "UIViewController". Interesting, can a view controller own (use) an IB-file? Well, apparently... Which view controller is it then? Well, it's "View Controller (Second)" in MainWindow.xib since we specified in the attributes window (CMD-1) that it should use an IB file called "SecondView". Sorry, no magic here either, just plain old logic.</span></div><div><span class="Apple-style-span" style="font-size: medium;"><br /></span></div><div><span class="Apple-style-span" style="font-size: medium;">Ok, did you really understand what was written above? That the "File's owner" object in this IB file is of the class UIViewController? You did? Good, let's continue then. As you know by now, UIViewControllers has three standard outlets (CMD-4 to see them). CMD-2 shows that the 'view' property of this view controller is set to an object called "View" which exists in this IB file.</span></div><div><span class="Apple-style-span" style="font-size: medium;"><br /></span></div><div><span class="Apple-style-span" style="font-size: medium;">What does it actually mean to set the 'view' property of the "File's owner" object? It means that we will set the 'view' property of the object which "loads" this IB-file and use it to initialise itself. In our case that is the "View Controller (Second)" object in MainWindow.xib. If you have a keen memory you will remember that we said it was a bit strange that that view controller did'nt have its 'view' property set (see above) and that we would explain that later. Well, later is now, and this is how it is set. Pretty neat, right?</span></div><div><span class="Apple-style-span" style="font-size: medium;"><br /></span></div><div><span class="Apple-style-span" style="font-size: medium;">The remaining objects in this IB-file are more or less identical to the corresponding objects under the "First View Controller" branch in MainWindow.xib so I won't bore you with the details here.</span></div><div><span class="Apple-style-span" style="font-size: medium;"><br /></span></div><div><span class="Apple-style-span" style="font-size: medium;">That's it for now. I'll probably write a short post explaining some things in the source code for this template too, but this post focused on the Interface Builder stuff...</span></div><div><span class="Apple-style-span" style="font-size: medium;"><br /></span></div><div><br /></div></span></span></div></div></div></div></div></div></div></span></span></div></div></div>henrikbhttp://www.blogger.com/profile/05536290915355204969noreply@blogger.com2tag:blogger.com,1999:blog-3970047569105596105.post-60773518028382144712009-04-12T08:42:00.015+02:002009-04-19T16:41:40.644+02:00Tutorial: Analysing XCode's "Utility Application" iPhone-template source codeIn the last post, we looked at XCode's "Utility Application" template mainly in Interface Builder (IB), but this time we will take a better look at the actual source code in XCode. So either start a new "Utility Application" project named "Util1" or load the "Util1" project we created in the last post.<div><br /></div><div>We did look at some source in the last post and noticed that MainView.[hm], MainViewController.[hm], FlipsideView.[hm] and FlipsideViewController.[hm] all are "almost empty" or at least very unexciting. We also looked at Util1AppDelegate.[hm] and RootViewController.h in the last post, so that more or less leaves RootViewController.m for this post.<br /><div><br /></div><div><span style="font-size:130%;">Application Controllers/RootViewController.m</span></div><div><pre><pre><span style="font-family:Georgia,serif;"><br /></span>- (void)viewDidLoad {<span style="font-family:Georgia,serif;"><br /></span>[super viewDidLoad];<span style="font-family:Georgia,serif;"><br /></span>MainViewController *viewController = [[MainViewController alloc] initWithNibName:@"MainView" bundle:nil];<span style="font-family:Georgia,serif;"><br /></span>self.mainViewController = viewController;<span style="font-family:Georgia,serif;"><br /></span>[viewController release];<span style="font-family:Georgia,serif;"><br /></span>[self.view insertSubview:mainViewController.view belowSubview:infoButton];<span style="font-family:Georgia,serif;"><br /></span>}<br /></pre></pre></div><div><br /><br />RootViewController is a UIViewController which implements a method called 'viewDidLoad'. This methods gets called when a UIViewController object is created from a nib file, or actually after its 'view' is set. Here we override that method so that we also can get a chance to manipulate the newly created RootViewController object.<br /><br />The first thing we do is to call the viewDidLoad method in our super class (UIViewController) to take advantage of the initialisations it does. After that we allocate an object of class MainViewController and initialise it from a nib file called 'MainView' which is our Resources/MainView.xib file. That newly allocated MainViewController object is temporarily stored in a local pointer called 'viewController' and permanently stored in this object's property 'mainViewController'. After that we release our reference to the MainViewController object using the temporary variable. This is done because the 'mainViewController' property was flagged with the 'retain' keyword in RootViewController.h.<br /><br />Finally, we insert the view of the MainViewController object we just read from MainView.xib below the 'infoButton'. We insert it below so that the 'infoButton' still will be visible - otherwise it could be blocked by the main view. The reason for why you see no code which sets up the view of 'mainViewController' or the 'infoButton' is that all that is done automatically when the xib files are read. Ok, we actually did the work ourselves in Interface Builder (IB), so the magic part is that we don't have to write any Objective-C source code for it - instead we did some CTRL-dragging in IB.<br /><br />Ok, so when we implement the 'viewDidLoad' method we get the chance to do additions or changes to the UIViewController object which was read from a xib file. The reason for why we didn't do everything in IB is that some things are impossible to do in IB and others are just simpler to do in source code.<br /><br />After this, we have a RootViewController object in memory. Two of it's properties have been set; 'infoButton' was set in IB and 'mainViewController' was set in the 'viewDidLoad' method above. 'flipsideViewController' and 'flipsideNavigationBar' are still unset. We'll wait with those a while and instead take a look at our 'toggleView' action, which is implemented in the method 'toggleView'!<br /><br /><pre><br />- (IBAction)toggleView {<br />/*<br />This method is called when the info or Done button is pressed.<br />It flips the displayed view from the main view to the flipside view and vice-versa.<br />*/<br /></pre><br />Ok, this nice comment pretty well explains what this method does.<br /><br /><pre><br />if (flipsideViewController == nil) {<br /> [self loadFlipsideViewController];<br />}<br /></pre><br />Here we check if the 'flipsideViewController' property is set and if it isn't we call the 'loadFlipsideViewController' method which hopefully sets the property for us.<br /><br /><pre><br />UIView *mainView = mainViewController.view;<br />UIView *flipsideView = flipsideViewController.view;<br /></pre><br /><br />We get the view properties from our the two UIViewControllers that are involved in the flipping action - "main view" and "flipside view" and store them in two local pointers.<br /><br /><pre><br />[UIView beginAnimations:nil context:NULL];<br />[UIView setAnimationDuration:1];<br />[UIView setAnimationTransition:([mainView superview] ? UIViewAnimationTransitionFlipFromRight : UIViewAnimationTransitionFlipFromLeft) forView:self.view cache:YES];<br /></pre><br />Help! What on Earth is this? Well, it's actually one of the coolest features of the iPhone API since it allows you to easily implement all those incredibly cool graphical effects you see when you play around with your iPhone - for example flipping a screen over so you can look at the "back" of the screen. In iPhone language these effects are called "animations" since they, instead of making something happen immediately, make something happen smoothly over time, that is, animating it...<br /><br />Animations change something from an initial state to a final state. The initial state often is the current state so often it's enough to describe the final state. Since you might want to animate a lot of "things" at once you have to describe them at once so that one thing doesn't start animating (moving) before another. Therefore, you start an "animation block" by calling 'beginAnimations' in the UIView class. Each animation block also has a time over which the animation will happen, specified by calling 'setAnimationDuration' with a number of seconds as an argument (this is a float, so 0.1, 1, 1.5, etc. are all valid values.) You can also specify how you want to animate; that is what "graphical effect" we want to use. Here we either want to flip from the right or from the left depending if we're moving from the main view to the flipside view or vice versa.<br /><br />The line specifying "from right" or "from left" might be worth explaining. How do we decide if we're flipping from the "main view" or the "flipside view"? We check if the "main view" has a superview! If it does, it's the main view that is visible and we're flipping from the right. How did it get a superview? Remember the 'viewDidLoad' method we implemented above? In that we "inserted" the "main view" into the "root view" and thus the "main view" got its superview.<br /><br /><pre><br />if ([mainView superview] != nil) {<br /> [flipsideViewController viewWillAppear:YES];<br /> [mainViewController viewWillDisappear:YES];<br /> [mainView removeFromSuperview];<br /> [infoButton removeFromSuperview];<br /> [self.view addSubview:flipsideView];<br /> [self.view insertSubview:flipsideNavigationBar aboveSubview:flipsideView];<br /> [mainViewController viewDidDisappear:YES];<br /> [flipsideViewController viewDidAppear:YES];<br /><br />} else {<br /> [mainViewController viewWillAppear:YES];<br /> [flipsideViewController viewWillDisappear:YES];<br /> [flipsideView removeFromSuperview];<br /> [flipsideNavigationBar removeFromSuperview];<br /> [self.view addSubview:mainView];<br /> [self.view insertSubview:infoButton aboveSubview:mainViewController.view];<br /> [flipsideViewController viewDidDisappear:YES];<br /> [mainViewController viewDidAppear:YES];<br />}<br /></pre><br />In this fat block, we continue to set up the "final state" of our animation. Here too, we care about if we're animating from the "main view" to the "flipside view" or vice versa. As you can see(!) the code is almost self-explanatory thanks to the rather well chosen names of all the methods we call and all the objects we work with. What we basically do is removing everything that is associated with the view we are "flipping out" and adding everything associated with the view we're "flipping in".<br /><br /><pre><br />[UIView commitAnimations];<br />}<br /></pre><br />This ends our animation block and actually commits the animations we described for execution. Exactly when they will happen is outside our control, but you can trust the iPhone to do its best to make it look good ;)<br /><br />Ok, we still haven't seen how the 'flipsideViewController' and 'flipsideNavigationBar' properties of our RootViewController object gets set so let's take a look at the 'loadFlipsideViewController' method which is said to solve that problem for us.<br /><br /><pre><br />- (void)loadFlipsideViewController {<br /><br />FlipsideViewController *viewController = [[FlipsideViewController alloc] initWithNibName:@"FlipsideView" bundle:nil];<br />self.flipsideViewController = viewController;<br />[viewController release];<br /></pre><br />This is pretty similar to what we saw for the "main view" in the 'viewDidLoad' method. Allocate the object and initialise it using a nib file, in this case a FlipsideViewController from Resources/FlipsideView.xib.<br /><br /><pre><br />// Set up the navigation bar<br />UINavigationBar *aNavigationBar = [[UINavigationBar alloc] initWithFrame:CGRectMake(0.0, 0.0, 320.0, 44.0)];<br />aNavigationBar.barStyle = UIBarStyleBlackOpaque;<br />self.flipsideNavigationBar = aNavigationBar;<br />[aNavigationBar release];<br /></pre><br />Cool, here we're creating a UINavigationBar programmatically (in source code) and store a reference to it in the 'flipsideNavigationBar' property of our RootViewController.<br /><br /><pre><br />UIBarButtonItem *buttonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(toggleView)];<br /></pre><br />Add the "Done"-button and make it call 'toggleView' when pressed.<br /><br /><pre><br />UINavigationItem *navigationItem = [[UINavigationItem alloc] initWithTitle:@"Util1"];<br /></pre><br />Create the "title".<br /><br /><pre><br />navigationItem.rightBarButtonItem = buttonItem;<br />[flipsideNavigationBar pushNavigationItem:navigationItem animated:NO];<br />[navigationItem release];<br />[buttonItem release];<br />}<br /></pre><br />And close the case!<br /><br />That's pretty much it! It took an impressive amount of words to explain this little application, but hopefully it has helped your understanding of the Interface Builder, the iPhone API and how to "think" when creating iPhone applications. A fine tuned mix of source code and "pre-packed" objects created with Interface Builder is often the right way to go even though everything, of course, can done programmatically - completely in source code.<br /><br /></div></div>henrikbhttp://www.blogger.com/profile/05536290915355204969noreply@blogger.com0tag:blogger.com,1999:blog-3970047569105596105.post-73339179860950063662009-04-10T09:12:00.017+02:002009-04-19T16:42:03.780+02:00Tutorial: Analysing XCode's "Utility Application" iPhone-template in Interface Builder<p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica">In the last two posts we analysed XCode's "Window-Based Application" template, with an emphasis on understanding how to use Interface Builder (IB) during iPhone application development. That template is a good starting point, since it's very simply but yet makes rather good use of IB.</p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"><br /></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica">Now, we'll move onto something more exciting by analysing XCode's "Utility Application" template, so start up XCode, choose "File/New Project" from the menu and select the Utility Application project and name it Util1. This is a quite interesting project, since it has multiple classes, multiple xib-files and even some user interaction via a GUI button. Lets start by looking at the file structure in XCode:</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica">Util1</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"> Main View</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"> MainView.[hm]</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"> MainViewController.[hm]</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"> Flipside View</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"> FlipsideView.[hm]</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"> FlipsideViewController.[hm]</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"> Application Controllers</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"> RootViewController.[hm]</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"> Util1AppDelegate.[hm]</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"> Resources</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"> FlipSideView.xib</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"> MainView.xib</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"> MainWindow.xib</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"> Info.plist</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica">Ok, it seems as if there are six classes (MainView, MainViewController, FlipsideView, FlipsideViewController, RootViewController and Util1AppDelegate). and three xib-files (FlipsideView, MainView and MainWindow). The things we recongnize from before are Util1AppDelegate, MainWindow.xib and Info.plist.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica">Before we dive into the details, build and run the project by pressing CMD-Return to see what it does! A grey background with a small icon in the lower right corner should appear. The small icon has a lowercase "i" in it, which in iPhone language means that it is an "info-button". Press it and see what happens!</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica">Wow, it triggered a really nice graphical effect which "flipped" the grey screen away and flipped in a black screen with some kind of title bar containing the text "Util1" and a "Done"-button at the top of the screen. Let's press the "Done-button" and see what happens.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica">Cool, it flipped back to the original grey screen again! Ok, as you can see, this is a very nice application which probably would make anyone you showed it to drool in envy and bow in respect to you if you said that it was you who have made it!</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica">After watching this fantastic presentation we understand why some of the files have names with the word "Flipside" in them - they probably have something to do with the stuff that was "flipped in" when we pressed the info-button on the main screen. Hey, there is a main screen also? Ok, now we understand why some files have the word "Main" in their names! This flipping effect is actually designed to make it look like you can look at the backside of your main screen - almost like flipping your iPhone around in order to admire the beautiful Apple-logo ;)</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica">Ok, enough talk, let's start wading through the files.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px"><br /></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px"><span class="Apple-style-span" style="font-size:large;">Examining the source files</span></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"><span class="Apple-style-span" style="font-size:medium;">Resources/Info.plist</span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica">Double-click it and you'll see that "Main nib file base name" is set to MainWindow, which means that Resources/MainWindow.xib will be loaded automatically when starting the application.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica">Before we start analysing the xib-file, we'll take a look at the classes, because otherwise it might be a bit hard understanding the objects and connectionsin the xib-file.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"><span class="Apple-style-span" style="font-size:medium;">Application Controllers/Util1AppDelegate.h</span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco"><span style="color:#aa0d91;">@interface</span> Util1AppDelegate : NSObject <UIApplicationDelegate> {</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco">Ok, this is the object implementing the UIApplicationDelegate protocol...</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; color:#5c2699;"><span style="color:#000000;"> </span>UIWindow<span style="color:#000000;"> *</span><span style="color:#3f6e74;">window</span><span style="color:#000000;">;</span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco">and it has a window...</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; color:#3f6e74;"><span style="color:#000000;"> </span>RootViewController<span style="color:#000000;"> *</span>rootViewController<span style="color:#000000;">;</span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco">and something called a RootViewController. If you look in the file list in XCode, you'll see that this is one of the classes in our project so we'll dive into that one soon.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; color:#aa0d91;">@property<span style="color:#000000;"> (</span>nonatomic<span style="color:#000000;">, </span>retain<span style="color:#000000;">) </span>IBOutlet<span style="color:#000000;"> </span><span style="color:#5c2699;">UIWindow</span><span style="color:#000000;"> *</span><span style="color:#3f6e74;">window</span><span style="color:#000000;">;</span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco">We want to manipulate our GUI objects from IB so we declare accessor methods for 'window' and also tag it with IBOutlet so that IB will discover it.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; color:#3f6e74;"><span style="color:#aa0d91;">@property</span><span style="color:#000000;"> (</span><span style="color:#aa0d91;">nonatomic</span><span style="color:#000000;">, </span><span style="color:#aa0d91;">retain</span><span style="color:#000000;">) </span><span style="color:#aa0d91;">IBOutlet</span><span style="color:#000000;"> </span>RootViewController<span style="color:#000000;"> *</span>rootViewController<span style="color:#000000;">;</span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco">The same goes for the mystical 'rootViewController'.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><span class="Apple-style-span" style="font-size:medium;">Application Controllers/Util1AppDelegate.m</span><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco">- (<span style="color:#aa0d91;">void</span>)applicationDidFinishLaunching:(<span style="color:#5c2699;">UIApplication</span> *)application {</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco">We want to do something when the application has finished launching so we implement the appropriate method from the UIApplicationDelegate protocol.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"> </p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; color:#3f6e74;"><span style="color:#000000;"> [</span>window<span style="color:#000000;"> </span><span style="color:#2e0d6e;">addSubview</span><span style="color:#000000;">:[</span>rootViewController<span style="color:#000000;"> </span><span style="color:#2e0d6e;">view</span><span style="color:#000000;">]];</span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco">The mystical rootViewController has a reference to a UIView object, accessible by calling it's 'view' method. We want to add this view to our window in order to display it, which is accomplished by calling 'addSubView' on our UIWindow 'window'</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; color:#2e0d6e;"><span style="color:#000000;"> [</span><span style="color:#3f6e74;">window</span><span style="color:#000000;"> </span>makeKeyAndVisible<span style="color:#000000;">];</span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco">Calling makeKeyAndVisible on a UIWindow makes it visible as well as making it the "first responder" of events (touches).</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"><span class="Apple-style-span" style="font-size:medium;">Application Controllers/RootViewController.h</span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco"><span style="color:#aa0d91;">@interface</span> RootViewController : UIViewController {</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco">The mystical class RootViewController turns out to be a subclass of UIViewController. A UIViewController is primarly used if you have a full screen view, which we do, and using it instead of just a view has a couple of benefits, but we won't go into that now.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; color:#3f6e74;"><span style="color:#000000;"> </span><span style="color:#5c2699;">UIButton</span><span style="color:#000000;"> *</span>infoButton<span style="color:#000000;">;</span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco"> MainViewController *mainViewController;</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco"> FlipsideViewController *flipsideViewController;</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco"> UINavigationBar *flipsideNavigationBar;</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco">We have an UIButton called 'infoButton', two other view controllers and a navigation bar. Judging by their names the view controllers are used for the main screen and the flipside screen we saw when we tested the application.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco">}</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; color:#aa0d91;">@property<span style="color:#000000;"> (</span>nonatomic<span style="color:#000000;">, </span>retain<span style="color:#000000;">) </span>IBOutlet<span style="color:#000000;"> </span><span style="color:#5c2699;">UIButton</span><span style="color:#000000;"> *</span><span style="color:#3f6e74;">infoButton</span><span style="color:#000000;">;</span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; color:#3f6e74;"><span style="color:#aa0d91;">@property</span><span style="color:#000000;"> (</span><span style="color:#aa0d91;">nonatomic</span><span style="color:#000000;">, </span><span style="color:#aa0d91;">retain</span><span style="color:#000000;">) </span>MainViewController<span style="color:#000000;"> *</span>mainViewController<span style="color:#000000;">;</span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; color:#aa0d91;">@property<span style="color:#000000;"> (</span>nonatomic<span style="color:#000000;">, </span>retain<span style="color:#000000;">) </span><span style="color:#5c2699;">UINavigationBar</span><span style="color:#000000;"> *</span><span style="color:#3f6e74;">flipsideNavigationBar</span><span style="color:#000000;">;</span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; color:#3f6e74;"><span style="color:#aa0d91;">@property</span><span style="color:#000000;"> (</span><span style="color:#aa0d91;">nonatomic</span><span style="color:#000000;">, </span><span style="color:#aa0d91;">retain</span><span style="color:#000000;">) </span>FlipsideViewController<span style="color:#000000;"> *</span>flipsideViewController<span style="color:#000000;">;</span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco">We want all of our properties to be accessible by other classes so we declare them as properties, but we only mark 'infoButton' with IBOutlet so apparently that's the only thing we will manipulate from IB.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco">- (<span style="color:#aa0d91;">IBAction</span>)toggleView;</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco">IBAction instead of IBOutlet - that's something new! You'll have to read the full post before understanding this one, so for now you can ignore it.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"><span class="Apple-style-span" style="font-size:medium;">Main View/MainViewController.h</span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"><span class="Apple-style-span" style="font-size:medium;">Flipside View/FlipsideViewController.h</span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica">Both these just declare that they are subclasses of UIViewController.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"><span class="Apple-style-span" style="font-size:medium;">MainView/MainView.h</span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"><span class="Apple-style-span" style="font-size:medium;">FlipsideView/FlipsideView.h</span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica">Both these just declare that they are subclasses of UIView.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"><span class="Apple-style-span" style="font-size:medium;">MainView/MainView.m</span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"><span class="Apple-style-span" style="font-size:medium;">FlipsideView/FlipsideView.m</span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica">Both these are quite unexciting, but the following might be worth explaining</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco">- (<span style="color:#aa0d91;">id</span>)initWithFrame:(<span style="color:#5c2699;">CGRect</span>)frame {</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco">Initialising this view is done by passing it a CGRect which defines a rectangle called 'frame', which will be used to set the size of the view. The return type is 'id' since we will return an object - a MainView or FlipsideView object to be precise.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco"> <span style="color:#aa0d91;">if</span> (<span style="color:#aa0d91;">self</span> = [<span style="color:#aa0d91;">super</span> <span style="color:#2e0d6e;">initWithFrame</span>:frame]) {</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco">The CGRect 'frame' is immediately passed to it's superclass UIView to take advantage of the initialisation it already provides. If the UIView initialisation worked we'll get a non-null pointer to an UIView in 'self' - that's "us".</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; color:#aa0d91;"><span style="color:#000000;"> </span>return<span style="color:#000000;"> </span>self<span style="color:#000000;">;</span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco">Here we return a pointer to "ourselves" so our caller can use the object we now have successfully initialised.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica"><span class="Apple-style-span" style="font-size:medium;">Main View/MainViewController.m</span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco">- (<span style="color:#aa0d91;">id</span>)initWithNibName:(<span style="color:#5c2699;">NSString</span> *)nibNameOrNil bundle:(<span style="color:#5c2699;">NSBundle</span> *)nibBundleOrNil {</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco">Creating a MainViewController object will apparently be done by providing a nib (IB-file).</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco"> <span style="color:#aa0d91;">if</span> (<span style="color:#aa0d91;">self</span> = [<span style="color:#aa0d91;">super</span> <span style="color:#2e0d6e;">initWithNibName</span>:nibNameOrNil <span style="color:#2e0d6e;">bundle</span>:nibBundleOrNil]) {</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco">This nib is directly passed on to our superclass UIViewController to let that handle the initialisation.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco"><span class="Apple-style-span" style="font-size:medium;">Flipside View/FlipSideViewController.m</span></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco"><br /></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco">This class has no "init"-method like MainViewController. Instead it has a method called viewDidLoad. This method is implemented in UIViewController (our superclass) and we should override it, as we do here, if we want to change something after our view has been loaded from a nib.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco">Since we implement this method, it means that this view will be loaded from a nib from somewhere else in our application. </p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco">- (<span style="color:#aa0d91;">void</span>)viewDidLoad {</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; color:#2e0d6e;"><span style="color:#000000;"> [</span><span style="color:#aa0d91;">super</span><span style="color:#000000;"> </span>viewDidLoad<span style="color:#000000;">];</span></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco"> <span style="color:#aa0d91;">self</span>.view.backgroundColor = [<span style="color:#5c2699;">UIColor</span> <span style="color:#2e0d6e;">viewFlipsideBackgroundColor</span>]; </p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco">}</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco">We apparently want to change the background colour after loading, but to make everything work as intended we also call the 'viewDidLoad' method of our superclass to take advantage of the functionality it provides.</p> <p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><span class="Apple-style-span" style="font-size:x-large;">Examining of the xib-files</span></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px">Finally, we have looked at the basic funcionality of all the classes in our project. Let's move on to the xib-files!</p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><span class="Apple-style-span" style="font-size:large;">Resources/MainWindow.xib</span></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><span class="Apple-style-span" style="font-size:medium;">File's owner</span></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px">CMD-4 reveals that the object which is going to use ("own") this file is of the class "UIApplication", just like we've seen before. As we know by now, UIApplication objects has a delegate object which implements the UIApplicationDelegate protocol. Specifying the delegate object can be done fron IB by setting the 'delegate' outlet of an UIApplication object. CMD-2 reveals that this object's delegate outlet is set to "Util1 App Delegate".</p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><span class="Apple-style-span" style="font-size:medium;">Util1 App Delegate</span></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px">CMD-4 shows that the class of this object is "Util1AppDelegate". It also shows that this class has two outlets; 'rootViewController' which is of type "RootViewController" (one of the classes we defined ourselves in the project) and 'window' of class UIWindow. CMD-2 shows that 'rootViewController' is connected to "Root View Controller" and 'window' is connected to "Window".</p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><span class="Apple-style-span" style="font-size:medium;">Root View Controller</span></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px">CMD-4 gives us that this object is of the class "RootViewController" (one of our classes). and that it has an outlet called 'infoButton' of type UIButton. It also shows us something we haven't seen before - an "action" called 'toggleView' of type id. If you have forgotten where this come from, take a look at "Application Controllers/RootViewController.h" in XCode by pressing the small right-arrow icon in the title bar above the 'toggleView' row in the "Class actions" box in the Identity Inspector (CMD-4).</p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px">This is getting exciting, so let's have a look at the connections right away! Press CMD-2 and you'll see that 'infoButton' is connected to something called "Light Info Button" and 'toggleView' is connected to "Light Info Button Touch Up Inside". You might have noticed that this object apparently also has an outlet called 'view' which is connected to something called "View". These are here because "RootViewController" is a subclass of "UIViewController" and that's where the 'view' outlet comes from. In fact, UIViewController has two other outlets as well; 'navigationItem' and 'tabBarItem'. Take yet another look at the "Root View Controller" connections (CMD-2) and you'll find those two there as well, but not connected to anything.</p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px">By the way, if you want to verify that these outlets really come from UIViewController, bring up the Library (CMD-L) and drop a "View Controller" in the MainWindow.xib window. Select it and press CMD-4 and you'll see all the outlets mentioned above . When you're done verifying, select the "View Controller" icon and press backspace to get rid of it.</p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px">A short note on the section "Referencing outlets". Select "Util1 App Delegate" and press CMD-4. In the "Referencing outlet" section you'll see a "connection" that looks like this: 'delegate' -- 'File's owner". What this means is that the outlet 'delegate' in the object 'File's owner' points to this object. If you read it from right to left it becomes even clearer: "File's owner".delegate = "this object".</p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px">Ok, lets leave the generic UIViewController and return to our view controller. As mentioned above the 'view' outlet is connected to an object called "View", but how come we see no icon for it in the xib-file contents window ("MainWindow.xib"). That's because you can embed objects in other objects in IB and that is exactly what the author of this xib-file has done.</p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px">To reveal these embedded objects, we have to change the view mode of the contents window, which you do by pressing one of the small icons in the upper left part of the window - just above the text "View mode". Press the middle one and you'll be presented with a hierarchical navigation view mode (tree mode). Click the arrow to the left of "Root View Controller" and the mystical "View" object should appear. Press the arrow to the left of that one and the even more mystical object called "Light Info Button" should appear. If you are really really observant you could also find these - or at least the info button - by double clicking the "Root View Controller" icon in the "normal" view mode of the contents window. (Hint: it's in the lower right corner of the window that appears if you try it...)</p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px">By they way, if you wonder about how to embed an object into another object you do that by dragging the object you want to embed onto the other object. That's how View was put into "Root View Controller". For buttons and elements where the position is important, you can also position them at the same time as you embed them. Try that by double-clicking "View" in the tree view. This should bring up a "blank window". The info button already present is barely visible in the lower right corner. Bring up the Library (CMD-L) and drag a "Segmented Control" from the "Inputs & Values" section into the blank view. It should immediately appear and you can position it and size it at will. Check the tree view of the contents window to confirm that it appeared under View in the tree. Remove it by selecting it and pressing backspace, when you're done playing.</p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><span class="Apple-style-span" style="font-size:medium;">View</span></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px">When you are in the "tree mode" it becomes easy to select the View icon so do that and press CMD-4 and then CMD-2. Nothing exciting, it's a simple UIView object with no outlets and no connections.</p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><span class="Apple-style-span" style="font-size:medium;">Light Info Button</span></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px">In tree mode it's just as easy to select the "Light Info Button" icon and press CMD-4. Hey, not even this was that exciting - it's just a simple "UIButton"! Ok, maybe CMD-2 will make things a bit more fun? Wow, look at the list of... "stuff" that appears! If you look closer you'll see that the "stuff" actually are called "Events" and apparently we have done something with one of them - "Touch Up Inside". It looks like this event is "connected" to "Root View Controller toggleView". Sounds familiar? It should, because we mentioned it above. We earlier saw that we defined an IBAction called 'toggleView' in RootViewController.h, something which you can also see by selecting the "Root View Controller" icon here in IB and pressing CMD-2. Cool! It thus seems as if "events" are connected to "actions". Sounds reasonable!</p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px">Let's dig deeper into this. If you bring up XCode and do a project-search for 'toggleView' by pressing CMD-F you'll see that it is defined in RootViewController.h simply as -(IBAction)toggleView and then it is implemented as a method in RootViewController.h using the same "signature". Could it be that this method gets called when the info button is pressed. Yes, that's right! Or actually it gets called when the "Touch Up Inside" event is generated by the button, which means that you have to touch the button ("touch down") and remove your finger ("touch up") while the finger is still "inside" (hoovering over) the button.</p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px">One mystery remains for the infoButton. How did the iPhone know that we wanted a button which looked liked a the letter "i" inside a ring? Press CMD-1 to find out. Here you'll see that the type is specified as "Info Light" and that explains that mystery!</p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><span class="Apple-style-span" style="font-size:medium;">Window</span><br /></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px">Double click the Window icon and a black window appears. CMD-4 reveals that it is a plain UIWindow and CMD-1 confirms that the background colour is set to black.</p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><span class="Apple-style-span" style="font-size:large;">Resources/MainView.xib</span></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><span class="Apple-style-span" style="font-size:18px;"><br /></span></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><span class="Apple-style-span" style="font-size:medium;">File's owner</span></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px">CMD-4 shows us something new. The "File's owner" objects we have worked with so far have all been UIApplications, but this one is of the class "MainViewController", which is one of our own classes. We see that it has no outlets of its own, but if we switch to the connections tab (CMD-1) we see that the three outlets inherited from its superclass UIViewController are there; 'navigationItem', 'tabBarItem' and 'view'. The 'view' outlet of the superclass is connected to something called "Main View" and if you take a look at the xib-file contents window (MainView.xib), you'll see where "Main View" came from - yes there is an object in this xib called just that.</p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><span class="Apple-style-span" style="font-size:medium;">Main View</span></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px">CMD-4 says this object is of class "MainView" and that it has no outlets of its own. CMD-2 shows that there are no inherited outlets as well. However, we see that there exists a connection via a "Referencing outlet". Using the syntax from above, this means that "File's owner".view = "this object". In our case "File's owner" is of the class "MainViewController", which in its turn is of the class "UIViewController", which in its turn has an outlet called 'view and that's the outlet that this object is connected to.</p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><span class="Apple-style-span" style="font-size:large;">Resources/FlipsideView.xib</span><br /></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><span class="Apple-style-span" style="font-size:medium;">File's owner</span></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px">CMD-4 shows that the class of the object is "FlipsideViewController" and that it has no outlets of its own. CMD-2 shows that the standard UIViewController outlets are there and that one of them, 'view', is connected to "Flipside View"</p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><span class="Apple-style-span" style="font-size:medium;">Flipside View</span></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px">CMD-4 gives us a class of "FlipsideView" and no outlets. CMD-2 shows a "referencing outlet" meaning that "File's owner".view = "this object".</p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><span class="Apple-style-span" style="font-size:x-large;">Summary</span></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px">Wow, what a ride! We have learned a lot this time, but even after digging through so much one mystery remains and that is how the "title bar" and "Done"-button on the "flipside" was created since we saw no trace of them in Interface Builder, not even after learning about the "tree view" of the xib-file contents window. Well, that's beacuase those GUI elements were created programmatically, that is by writing Objective-C source code, but we won't analyse that here since we're now trying to focus on the IB. If you're curious about how that works I suggest you go back to XCode and do a project search (CMD-F and remember to enable "ignore case") for "navigation". Hmm, that sounded like an excercise for the reader to me ;) Good luck!</p><p style="margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Monaco; min-height: 14.0px"><br /></p>henrikbhttp://www.blogger.com/profile/05536290915355204969noreply@blogger.com3tag:blogger.com,1999:blog-3970047569105596105.post-86990518615173496102009-04-10T08:27:00.010+02:002009-04-19T16:42:22.705+02:00Tutorial: Creating an iPhone xib-file from scratch in Interface Builder (IB)In the last post we analysed the contents of MainWindow.xib in order to try to understand how Interface Builder (IB) works and what you can use it for. This time we will try to create an IB-file from scratch in IB, since syntehsizing (creating) something often is the best way to confirm that you really understood everything you learned while analysing a subject.<div><br /></div><div>Start IB, choose File/New from the meu and select the "Empty" template. This creates a xib called "Untitled" which contains "File's owner" and "First responder".</div><div><br /></div><div>Select "File's owner" and press CMD-4 and you'll see that it is of the class NSObject. The owner of the xib-file we are currently creating is an object of type UIApplication, so we should start by changing the class to that. To do that, press CMD-4 and choose UIApplication from the drop-down list.</div><div><br /></div><div>As soon as you do this an outlet called 'delegate' is automatically added in the "Class outlets" section. This is because an UIApplication normally is connected to an object which implements the UIApplicationDelegate protocol and that's where the 'delegate' name comes from.</div><div><br /></div><div>The next natural step therefore is to create an object which implements that protocol. We have aldready defined such a class in XCode called "Test1AppDelegate" (see last post), so we'll create an object of that class.</div><div><br /></div><div>Choose "Tools/Library" from the menu or press CMD-L. Select "Cocoa Touch Plugin/Controllers/Object" and drag the object icon into the "Untitled" window. This creates an object simply called "Object" in our IB-file.</div><div><br /></div><div>CMD-4 reveals that this newly added object is of class NSObject, but we want it to be of class "Test1AppDelegate". Since this is a non-standard class that exists only in our project, we need to import the class definition/specification so that IB understands how to use it. To do this, choose "File/Read Class File" from the menu and navigate to Classes/Test1AppDelegate.h and select it.</div><div><br /></div><div>Select "Object" again from the file contents window ("Untitled") and press CMD-4. Now you should be able to select "Test1AppDelegate" as the class for this object. You should also see that this class has one outlet of type UIWindow with the name'window', which should be familiar since that is our window.</div><div><br /></div><div>Let's connect the 'window' outlet to an UIWindow! To do this, we first have to create an UIWindow object, so press CMD-L to bring up the Library window. Choose "Windows, Views and Bars" and drag the Window-icon into the file contents window ("Untitled"), that is, drop it right beside the "Test1 App Delegate" object we just created.</div><div><br /></div><div>To connect the 'window' outlet of "Test1 App Delegate" to the UIWindow object we just created, position the mouse over the "Test1 App Delegate" icon and <span class="Apple-style-span" style="font-weight: bold;">press and hold the CTRL-key.</span> Keep on holding the CTRL-key and <span class="Apple-style-span" style="font-weight: bold;">press and hold the mouse button</span> while moving the mouse pointer towards the Window icon. A blue line should appear, going from the the icon to the current position of the mouse pointer. Release the mouse button when the pointer is positioned over the Window icon.</div><div><br /></div><div>Now a small pop-up window called "Outlets" should appear. Select the 'window' outlet, which should connect the Window object to the 'window' outlet of "Test1 App Delegate". To verify this, select "Test1 App Delegate" and press CMD-2. The 'window' outlet should have the value "Window", which is the IB-name of the UIWindow object we created a minute ago.</div><div><br /></div><div>Now we should make "Test1 App Delegate" the delegate of our UIApplication, which is called "File's owner" here. Repeat the CTRL-drag procedure from above, but this time drag from "File's owner" to "Test1 App Delegate" and select the 'delegate' outlet. Done!</div><div><br /></div><div>This procedure of CTRL-dragging between icons to connect an object to an outlet is in my view a bit counter-intuitive since I think the direction you have to drag the mouse is "wrong". To connect one of object A's outlet to object B, you should CTRL-drag from A to B. For me, it would have been more intuitive to instead CTRL-drag from B to A, if I wanted to connect B to A, but I guess that's just a matter of perspective.</div><div><br /></div><div>To help us verify that we have actually succeeded in re-creating a "copy" of the MainWindow.xib file that was automatically created for us by XCode, click the Window icon and press CMD-1. Locate the box where you can set the window's background colour and set it to green or some other colour than the default white. As a final step, save the xib as "MyMainWindow" in the "Test1AppDelegate" project. IB will ask if you want to add it to your project which is exactly what we want so answer yes.</div><div><br /></div><div>Go back to XCode and drag the MyWainWindow.xib file to the resources folder and then click on Resources/Info.plist to change MainWindow to MyMainWindow. This instructs iPhone to use our re-created xib-file instead of the one that XCode created for us.</div><div><br /></div><div>Build and run the project by pressing CMD-Return and you should be presented with a green screen instead of a white one. Wow, we managed to quite easily create our own xib-file from scratch and "integrate" it in the application we created in XCode!</div><div><br /></div><div><br /></div>henrikbhttp://www.blogger.com/profile/05536290915355204969noreply@blogger.com2tag:blogger.com,1999:blog-3970047569105596105.post-4181982329564121902009-04-05T14:45:00.011+02:002009-04-19T16:42:36.696+02:00Tutorial: Understanding the iPhone Interface Builder (IB)<span class="Apple-style-span" style="font-size:small;">In november 2008, I decided to start exploring the Apple iPhone development environment. The main reason for doing that was that I wanted to port a mobile game I wrote for the Mophun platform five years ago. Another reason was that I thought the iPhone seemed like an exciting platform.<br /><br />Before starting to code I took the time to read quite a lot of official documentation from Apple. The primary problem with the Apple iPhone documentation in my view is that there is so much of it that it becomes hard to choose what to read. A secondary, but quite important, problem is that a lot of the documentation contains the wrong information, at least for someone who isn't familiar with Mac development. That the documents contain a lot of words, but little information is also quite frustrating. Thankfully, the API documentation available in XCode is much better.<br /><br />However, I managed to find a handful of good documents and after reading them I thought I was well prepared for iPhone development. At this time there was almost no tutorials, blogs or discussion forums on the Internet since the Apple NDA had just been lifted. Therefore I started to explore on my own and got a complete chock when I started up the Interface Builder (IB). I didn't understand anything, which made me feel like a complete fool. Especially after spending so much time reading documents.<br /><br />I quickly gave up the IB and started doing things programmatically instead, that is, doing everything by hand in source code; creating windows, views, buttons, etc. This turned out to be really simple so my self confidence was (partly) restored. It also made me feel that the problem wasn't my lack of understanding, but the lack of good documentation for the IB - see what a little self confidence can do ;)<br /><br />A few days ago, I decided to return to IB and make a new attempt at understanding it. This time it went a little better since I had gained a lot of real iPhone experience since the last attempt. To force myself to really understand it, I decided to start by trying to fully explain the code and xib-files created by the XCode project templates.<br /></span><br /><div><span class="Apple-style-span" style="font-size:large;">Create the project.</span><br /><div><ul><li><span class="Apple-style-span" style="font-size:small;">Start XCode or choose "File/New project" from the menu-bar if XCode already is running.</span></li><li><span class="Apple-style-span" style="font-size:small;">In the left column, select: "iPhone OS/Application".</span></li><li><span class="Apple-style-span" style="font-size:small;">In the right box, select: "Window-Based application".</span></li><li><span class="Apple-style-span" style="font-size:small;">In the right bottom of the window, click the "Choose"-button.</span></li><li><span class="Apple-style-span" style="font-size:small;">Rename the project to "Test1" (that's the figure one, not the letter ell) by replacing the text "Untitled" in the "Save as"-box and save the project an appropriate place and press the "Save"-button.</span></li></ul><div><span class="Apple-style-span" style="font-size:small;">After completing these steps, XCode will create a set of files for you which are displayed in the left column of the window using an expandable tree-view. Below is the hierarchy of files we will concentrate on in this post:</span></div><div><span class="Apple-style-span" style="font-size:small;"><br /></span></div><div><span class="Apple-style-span" style="font-size:small;">Test 1</span></div><div><span class="Apple-style-span" style="font-size:small;"> Classes</span></div><div><span class="Apple-style-span" style="font-size:small;"> Test1AppDelegate.h</span></div><div><span class="Apple-style-span" style="font-size:small;"> Test1AppDelegate.m</span></div><div><span class="Apple-style-span" style="font-size:small;"> Resources</span></div><div><span class="Apple-style-span" style="font-size:small;"> MainWindow.xib</span></div><div><span class="Apple-style-span" style="font-size:small;"> Info.plist</span></div><div><br /></div><div><span class="Apple-style-span" style="font-size:x-large;">Explanation of the files</span></div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;">Classes/Test1AppDelegate.h</span></div><div><br /></div><div><span class="Apple-style-span" style="font-size:small;">This is the h-file (specification) for the class in our application which will handle events/messages from the UIApplication-object which exist in all iPhone applications. Let's look at the most import lines of code.</span></div><div><span class="Apple-style-span" style="font-size:small;"><br /></span></div><div><code><span class="Apple-style-span" style="font-size:small;">Test1AppDelegate : NSObject <UIApplicationDelegate> {</span></code></div><div><span class="Apple-style-span" style="font-size:small;">Specifies that this object inherits from NSObject and implements the UIApplicationDelegate protocol. (A protocol is similar to an interface in Java.)</span></div><div><span class="Apple-style-span" style="font-size:small;"><br /></span></div><div><code><span class="Apple-style-span" style="font-size:small;">UIWindow *window;</span></code></div><div><span class="Apple-style-span" style="font-size:small;">The object will make use of an UIWindow which is the "base GUI component" in an iPhone and container for all other GUI components. Therefore we create a pointer to our window, which we will call 'window'.</span></div><div><span class="Apple-style-span" style="font-size:small;"><br /></span></div><div><code><span class="Apple-style-span" style="font-size:small;">@property (nonatomic, retain) IBOutlet UIWindow *window;</span></code></div><div><span class="Apple-style-span" style="font-size:small;">Properties are more or less instructions to the compiler to generate accessor methods (getters and setters) for a property (member field/variable) of the object. Here we want our variable 'window' to be accessible to other objects, so the compiler will create declarations for it here in the h-file. We also flag the property with 'IBOutlet' so that we can manipulate the it using IB.</span></div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;">Classes/Test1AppDelegate.m</span></div><div><br /></div><div><span class="Apple-style-span" style="font-size:small;">This is the m-file (implementation) of the class Test1AppDelegate.</span></div><div><span class="Apple-style-span" style="font-size:small;"><br /></span></div><div><code><span class="Apple-style-span" style="font-size:small;">synthesize window</span></code></div><div><span class="Apple-style-span" style="font-size:small;">Instructs the compiler to create the actual accessor method (set/get) definitions (implementation), according to the instructions in the @property declaration in the h-file.</span></div><div><span class="Apple-style-span" style="font-size:small;"><br /></span></div><div><code><span class="Apple-style-span" style="font-size:small;">- (void)applicationDidFinishLaunching:(UIApplication *)application {</span></code></div><div><span class="Apple-style-span" style="font-size:small;">This method is part of the UIApplicationDelegate protocol and gets called when the application has finished launching(!).</span></div><div><span class="Apple-style-span" style="font-size:small;"><br /></span></div><div><code><span class="Apple-style-span" style="font-size:small;">[window makeKeyAndVisible]</span></code></div><div><span class="Apple-style-span" style="font-size:small;">This line calls the method makeKeyAndVisible in our window 'window'. What might seem strange here is that we so far hasn't seen any code for creating the window, just a declaration of the window in the h-file. This is because we don't create this window programmatically (in source code), but instead use IB to create, but more on that later.</span></div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;">Resources/MainWindow.xib</span></div><div><br /></div><div><span class="Apple-style-span" style="font-size:small;">This is an IB-file, which was created automatically by XCode when we chose to create a "Window-Based Application" project. We will return to this file soon.</span></div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;">Resources/Info.plist</span></div><div><br /></div><div><span class="Apple-style-span" style="font-size:small;">This is a "property list" which lists same basic properties for the application we are creating. Click it to see its contents. The most important property is the "Main nib file base name" which is set to "MainWindow", meaning that it points to the file Resources/MainWindow.xib in our project. The xib-file listed in the property will be automatically loaded by the iPhone when the application is started.</span></div><div><br /></div><div><span class="Apple-style-span" style="font-size:x-large;">A first test run</span></div><div><br /></div><div><span class="Apple-style-span" style="font-size:small;">In order to see some action, lets try to build and run our application to see what it does. In XCode, choose "Build/Build and Go" from the menu or simply press CMD-Return. This should bring up the iPhone simulator and display a completely white screen. Not too exciting, but quite impressive considering we still haven't written a single line of code ourselves. Press the Home-button on the iPhone simulator and navigate to the last page of icons on the home screen and you should see an icon called 'Test1' - yes, that's our little application!</span></div><div><br /></div><div><span class="Apple-style-span" style="font-size:x-large;">Interface Builder (IB)</span></div><div><br /></div><div><span class="Apple-style-span" style="font-size:small;">Ok, now it's time to start exploring the IB. First of all, the most important thing to understand about the IB is that </span><span class="Apple-style-span" style="font-weight: bold;"><span class="Apple-style-span" style="font-size:small;">it creates actual Obejctive-C objects and not source code!</span></span><span class="Apple-style-span" style="font-size:small;"> It can also set the properties of the objects, for example to "connect" two objects two eachother (by setting a pointer in object A to point to object B). The created objects, including the values of their properties, are then saved into a xib-file which can be loaded by the application. When the file is loaded the objects are re-created by the application in run-time. (For Java-coders, this is similar to reading a file containing serialised objects.)</span></div><div><span class="Apple-style-span" style="font-size:small;"><br /></span></div><div><span class="Apple-style-span" style="font-size:small;">To start IB, double-click Resources/MainWindow.xib. This will start IB and bring up some windows, where the window titled "MainWindow.xib" is the starting point. This window displays the content of a xib-file, in this case our xib-file called MainWindow.xib.</span></div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;">File's owner.</span><br /></div><div><br /></div><div><span class="Apple-style-span" style="font-size:small;">This object represents the object which eventually will load this xib-file - the object which "owns" this xib-file - and that is why it has the very confusing name "File's owner".</span></div><div><span class="Apple-style-span" style="font-size:small;"><br /></span></div><div><span class="Apple-style-span" style="font-size:small;">In our case, the real object owning this xib-file is the UIApplication automatically created by the iPhone when our application is started. The UIApplication object knows it should load this xib-file since we specified it in the Info.plist file.</span></div><div><span class="Apple-style-span" style="font-size:small;"><br /></span></div><div><span class="Apple-style-span" style="font-size:small;">The most important tool in IB is the "Inspector window" which can be invoked from the "Tools/Inspector" menu. It has four "tabs" called "Attributes", "Connections", "Size" and "Identity", which can be reached directly from anywhere in IB by clicking on an object icon and then pressing CMD-1 to CMD-4 respectively. In my view, "Identity" (CMD-4) is the best place to start since it tells us what kind of object we are dealing with.</span></div><div><span class="Apple-style-span" style="font-size:small;"><br /></span></div><div><span class="Apple-style-span" style="font-size:small;">So, click on "File's owner" and press CMD-4. Here we can see that this object is of the class UIApplication. We can also see that this is where the strange name "File's owner" comes from. The most interesting thing we see, though, is that it has a property (or outlet as it is called here) called 'delegate' which has the Objective-C type 'id'. As you probably know, 'id' is an "object pointer", which means that this property should be set to another object. In this case it should be set to an object which implements the UIApplicationDelegate protocol.</span></div><div><span class="Apple-style-span" style="font-size:small;"><br /></span></div><div><span class="Apple-style-span" style="font-size:small;">To see the value of the 'delegate' property, press CMD-2 to invoke the "Connections"-tab. Here we can see that 'delegate' is set to 'Test 1 App Delegate', which might sound familiar to you if you looked at the other objects in the IB window 'MainWindow.xib' which displays the contents of the xib-file. Thus, it seems as if there exists a connection between the objects "File's owner" and "Test1 App Delegate" which both exist in our xib-file. Please note that the names "File's owner" and "Test1 App Delegate" are IB-names, not the real names of the objects. To see the real names of the objects, or actually the class of the objects, select an object and press CMD-4.</span></div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;">First Responder.<br /></span></div><div><span class="Apple-style-span" style="font-size:18px;"><br /></span></div><div><span class="Apple-style-span" style="font-size:large;"><span class="Apple-style-span" style=" ;"></span></span></div><div><span class="Apple-style-span" style="font-size:small;">I haven't fully understood the role of this yet, so I will try to explain it later...<br /></span></div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;">Test1 App Delegate.</span><br /></div><div><span class="Apple-style-span" style=" ;font-size:16px;"></span></div><div><br /></div><div><span class="Apple-style-span" style="font-size:small;">Ok, let's check the "Identity Inspector" for this one by pressing CMD-4. We see that this object is of the class Test1AppDelegate and that it has an outlet called 'window'. That's the class we defined in Classes/Test1AppDelegate.h and Classes/Test1AppDelegate.m! As you can see, our UIWindow-property 'window' also showed up, thanks to the IBOutlet in Classes/Test1AppDelegate.h</span></div><div><span class="Apple-style-span" style="font-size:small;"><br /></span></div><div><span class="Apple-style-span" style="font-size:small;">If we check the object's connections by pressing CMD-2, we see that our 'window' property is set to "Window". This means that a UIWindow with an IB-name of "Window" has been created in IB and connected to our 'window' property. If we check the contents of the xib-file displayed in the MainWindow.xib window we see that there actually is an object there called Window. We can also see which objects reference this object, that is, the "reverse connections". In this case we can see that the "File's owner" object has a connection to this object as we explained above.</span></div><div><br /></div><div><span class="Apple-style-span" style="font-size:large;">Window.</span></div><div><div><br /></div><div><span class="Apple-style-span" style="font-size:small;">Now, we have finally reached a GUI-object! Select the Window icon and press CMD-4 right away. We see that the class of this object is UIWindow. Press CMD-2 to check the connections: apparently there exists a "reverse connection" from an object with the IB-name "Test1 App Delegate". This means that "Test1 App Delegate" uses this Window.</span></div><div><span class="Apple-style-span" style="font-size:small;"><br /></span></div><div><span class="Apple-style-span" style="font-size:small;">For an object of UIWindow class, the Attributes (CMD-1) and Size (CMD-3) tabs also contain information. Pressing CMD-1 we can see that the background colour has been set to white, so finally we find the explanation for why we saw a completely white display when we test run our application previously! Pressing CMD-3 we can see that the size is set to 320x480 which completely fills the iPhone's display, which always should be the case for windows.</span></div><div><span class="Apple-style-span" style="font-size:small;"><br /></span></div><div><span class="Apple-style-span" style="font-size:small;">Finally, we have now explained the contents of the xib-file! Do remember that when the xib-file is loaded, the Objective-C objects it contains are created and the properties set in IB are set in the created objects as well. This is the explanation for how so little source code can accomplish so much.</span></div><div><br /></div></div></div></div>henrikbhttp://www.blogger.com/profile/05536290915355204969noreply@blogger.com0tag:blogger.com,1999:blog-3970047569105596105.post-30571550292623298882009-04-05T12:22:00.000+02:002009-04-05T12:56:20.227+02:00Learning is my drug......but what really gives me a high is spreading the knowledge I acquire during my adventures in the world of software development. My favorite areas of exploration include abstract and "underexplained" things, but anything goes as long as I learn something from it.<br /><br />The subtitle of the blog - "When one teaches, two learns" - is a quote I once heard and really like. As I interpret it, it means that just by trying to explain something to someone else, you, the teacher, will learn even more. This is something most people have experienced since explaining something and answering questions about it often exposes your own "weak spots" - perhaps you didn't understand it as good as you thought? Well, it's a nice quote, so let's not over analyze it. By the way, according to a Google search it seems as if it was Robert Heinlein who first said it.<br /><br />Coming from a low level background of mainly C and assembler programming on everything from Commodore Amigas to proprietary embedded systems running real-time operating sytems, I will try give this blog a somewhat different perspective compared to the thousands of other Java and iPhone resources and tutorials available.<br /><br />To summarise, in this blog, I will try to write down things I learn. At the moment I mostly work with Java SE and Apple iPhone development so I guess most of the posts will be related to those two.<br /><br />A final note. Since I often write down things as I learn them, I will make no claim that the texts I write are 100% correct. Instead they will reflect my current knowledge of the subject and I will probably update an entry as I learn more. Therefore, comments and corrections are highly appreciated!<br /><br />PS. As you see, there is one post with an older date than this one. I imported that from one of my other blogs, which now no longer exists...henrikbhttp://www.blogger.com/profile/05536290915355204969noreply@blogger.com0tag:blogger.com,1999:blog-3970047569105596105.post-25144143828436821412008-10-21T21:52:00.000+02:002009-04-05T14:45:22.612+02:00Weak referencesComing from a low level background, I have always thought that memory management (allocating and freeing memory), pointers and related concepts are quite natural. The fact that you should always free what have allocated is quite deeply rooted in my programmer's soul.<br /><br />Therefore, when starting to learn Java it was quite hard for me to stop thinking about this and instead trust the garbage collector to do the work for me.<br /><br />However, the more I learned and the more I read about Java I started to realise that you cannot completely forget about memory management. In fact, in some situations you have to think quite hard about it!<br /><br />This combined with the - for me - quite disturbing fact that most texts on Java go to great lengths to avoid even touching on the concept of "real memory" and pointers can easily lead to some confusion. The texts instead insist of talking about objects and references to objects, since that is the chosen abstraction level of the language and the only thing a Java programmer should be concerned about.<br /><br />For the most part, references work really well and are indeed uncomplicated to use, but instead of managing memory you now have to manage references. This is because a reference is a kind of a "smart pointer" which keeps track of how many references there are to a given block of memory (an object) and the memory block isn't freed until the number of references reaches zero.<br /><br />If you "forget" a reference somewhere, the reference counter will never reach zero and the associated memory won't be freed. A memory leak has just occured!<br /><br />A typical example of where it is easy to forget a reference is in a hash map. If the hash map isn't the primary "storage point" of the object but just a way to associate the object with some key (an id for example) it is very easy to forget to remove the reference from the map once the object isn't needed anymore. As long as the map has a reference to the object, the object's reference counter won't reach zero and the object's memory won't be freed.<br /><br />To make things worse, each entry in an map has two references; one to the key object and one to the value object. Thus, there are two references with the risk of being forgotten.<br /><br />In Java it is especially easy to forget to remove these references, since the language often handles it for you (for example when you assign a new value to a refernce) and you aren't supposed to think too hard about it. In low level programming where you manage memory manually you think about this all the time and it's therefore less likely that you forget a reference somewhere.<br /><br />The example below illustrates this problem by creating a hash map and then entering an endless loop which creates a string object (value) and an id object (key) for the string and then adds the {id, string} entry to the map.<br /><br />The main storage point of the string in this example is the local reference 'str'. Until 'str' is overwritten in the next iteration of the loop, there exists two references to the string; 'str' and the map entry's ref. When 'str' is overwritten, only one reference exists. However, the reference count never reaches zero and thus, sooner or later we will get an OutOfMemoryError exception. The id object's reference 'key' work exactly the same way.<br /><br /><pre><br />import java.util.Map;<br />import java.util.HashMap;<br /><br />/**<br /> * key value<br /> * 0 "string for id 0"<br /> * 1 "string for id 1"<br /> * ...<br /> */<br />public class Hash {<br /> public static void main(String[] args) {<br /> Map<Long, String> map = new HashMap<Long, String>();<br /> long id = 0;<br /> while ( true ) {<br /> Long key = new Long(id); // Long(id) object's ref count = 1<br /> String str = "string for id " + id; // "string..." object's ref count = 1<br /> map.put(key, str); // Long(id) ref. cnt. = 2, "string..." ref count = 2<br /> id++;<br /> }<br /> }<br />}<br /></pre><br /><br />To solve this, you have to explicitly remove the reference from the map by calling map.remove(key) or clearing the whole map with map.clear(). The first alternative is demonstrated in the slightly modified version of the program below.<br /><br /><pre><br />import java.util.Map;<br />import java.util.HashMap;<br /><br />/**<br /> * key value<br /> * 0 "string for id 0"<br /> * 1 "string for id 1"<br /> * ...<br /> */<br />public class HashWithRemove {<br /> public static void main(String[] args) {<br /> Map<Long, String> map = new HashMap<Long, String>();<br /> long id = 0;<br /> while ( true ) {<br /> Long key = new Long(id); // ref count = 1<br /> String str = "string for id " + id; // ref count = 1<br /> map.put(key, str); // ref counts = 2<br /> id++;<br /> map.remove(key); // ref counts = 1<br /> }<br /> }<br />}<br /></pre><br /><br />This manual management of references is a bit un-Java like, so therefore there exists an alternative solution which uses so called weak references (<a href="http://java.sun.com/javase/6/docs/api/java/lang/ref/WeakReference.html">WeakReference</a>)and a special hash map which uses weak references internally (<a href="http://java.sun.com/javase/6/docs/api/java/util/WeakHashMap.html">WeakHashMap</a>).<br /><br />Simply put, a weak reference is a way to keep a reference to an object, while saying that you don't really care if the object is still available when you "need" it (try to access it) sometime in the future. Clearly, you should not use weak references for objects that you actually do need in the future. For such objects, you should still use normal <span style="font-style:italic;">strong</span> references.<br /><br />By simply exchanging the HashMap with a WeakHashMap you don't have to worry about removing the reference from the map anymore, and you won't run out of memory any longer.<br /><br /><pre><br />import java.util.Map;<br />import java.util.WeakHashMap;<br /><br />/**<br /> * key value<br /> * 0 "string for id 0"<br /> * 1 "string for id 1"<br /> * ...<br /> */<br />public class WeakHash {<br /> public static void main(String[] args) {<br /> Map<Long, String> map = new WeakHashMap<Long, String>();<br /> long id = 0;<br /> while ( true ) {<br /> Long key = new Long(id); // strong reference<br /> String str = "string for id " + id; // strong reference<br /> map.put(key, str); // weak references<br /> id++;<br /> }<br /> }<br />}<br /></pre><br /><br />What happens in the WeakHashMap version is that the <span style="font-style:italic;">strong references</span> 'str' and 'key' are removed, while the <span style="font-style:italic;">weak references</span> in the map are kept. Instead of running out of memory the garbage collector will automatically remove the weak references to the objects and thus bring the reference counts down to zero and freeing the objects' memory.<br /><br />This means that the entry for the object in the map can suddenly "disappear". If you try to retrieve the object from the map using its key the map.get(key) method will simply return null.<br /><br />The program below demonstrates this "disappearance" by continuously checking the value for the key 0 and printing a message when it no longer can get it from the map.<br /><br /><pre><br />import java.util.Map;<br />import java.util.WeakHashMap;<br /><br />/**<br /> * key value<br /> * 0 "string for id 0"<br /> * 1 "string for id 1"<br /> */<br />public class WeakHashCheck {<br /> public static void main(String[] args) {<br /> Map<Long, String> map = new WeakHashMap<Long, String>();<br /> long id = 0;<br /> boolean isCheck = true;<br /> while ( true ) {<br /> Long key = new Long(id);<br /> String str = "string for id " + id;<br /> map.put(key, str);<br /> if ( isCheck && map.get(new Long(0)) == null ) {<br /> isCheck = false;<br /> System.out.println("*** key 0 no longer available in map; current id=" + id);<br /> }<br /> id++;<br /> }<br /> }<br />}<br /></pre><br /><br />For situations like these, when you want to keep a map of objects alongside with the primary storage of the objects, weak references in general and WeakHashMap in particular provide a very elegant solution to an otherwise quite messy memory management problem.<br /><br />For my own part, after discovering this, I have a new tool in my Java toolbox which I hopefully will find some use for in the future.henrikbhttp://www.blogger.com/profile/05536290915355204969noreply@blogger.com0