Wednesday, June 30, 2010

iPhone 4 review

Nintendo-3DSMicrosoft am E3Engadget Karte HTC EVO 4GApple iPadNewsHubsGalleriesVideosPodcastsThe RecapAuthorsFOLLOW US ON TWITTERSUBSCRIBEABOUT / FAQTIP U.S. .at15t_email

Announcing OpenGL ES 2.0 for iOS 4

type='html'>Okay, it's finally time for me to announce Super Secret Project B, which is a new book I'm writing on OpenGL ES 2.0 for iPhone, iPad, and iPod touch.

Yes, I know I said I wouldn't be writing any books in 2010, but the nice folks at the Pragmatic Programmers approached me after I made that statement with an economically feasible way for me to write a book this year. I couldn't say no.

My original plan had been to take my OpenGL ES from the Ground Up blog posts, supplement them, and turn them into a book with step-by-step projects to reinforce the points of each posts. My week at WWDC has caused me to change that approach. I went to all the OpenGL ES sessions and spent a fair amount of time bending the ear of Allan Schaffer, Apple's Graphics and Games Evangelist, as well as a number of Apple engineers who work on or with OpenGL. After a lot of hard thought, I came to the conclusion that the approach needed to change. Although modern hardware supports the fixed pipeline, Apple has stopped making phones that require OpenGL ES 1.1. The iPhone 3GS, iPhone 4, and iPad all not only support OpenGL ES 2.0, but they all require it if you want to take full advantage of the hardware.

OpenGL ES from the Ground Up, however, focused on the OpenGL ES 1.1 fixed pipeline.

Since OpenGL ES doesn't maintain backward compatibility the way workstation OpenGL does, much of the material from the fixed pipeline, such as lights, the model view and projection matrix, and the stock functions for submitting vertex, texture, and normal data are all gone from OpenGL ES 2.0. Instead, you have to handle all that work manually when you write your shaders. Shaders add some complexity to the process, but give you a tremendous amount of power and the ability to write more efficient OpenGL code.

In the Desktop world, it still makes sense to learn immediate mode first, then regular fixed pipeline, and then finally the programmable pipeline, because workstation OpenGL maintains nearly 100% backwards compatibility across releases, and you can get up and running quickly. On Workstation OpenGL ES, you can even mix and match the different modes, accessing the model view matrix and lights from your shaders. That's not the case with OpenGL ES, so spending a few hundred pages teaching things that won't be applicable to the programmable pipeline seemed like a poor use of time, both mine, and my readers'.

But, for the new programmer, the programmable pipeline is really hard to grok. I've been banging my head this past week looking for a way to present that rather complex topic in a way that people without prior graphics or OpenGL ES experience will be able to understand.

Thanks to a lot of people willing to talk with me, I think I've come up with an approach that will work, and I'm really excited about it. So, if you find OpenGL ES confusing, especially if you find the programmable pipeline and shaders confusing, look out for OpenGL ES 2.0 for iOS4. I don't have a release date yet, but I will post here when I have updated information about the production schedule.

WWDC 2010 Post Mortem

type='html'>WWDC. The Dubdub. Christmas in June.

If you've followed this blog for any length of time, you know Apple's annual developer conference is my absolute favorite week of the year, and it just seems to get better every year. For the days leading up to leaving for San Francisco, I'm like a kid on Christmas eve. I can't sleep from excitement and the time passes way too slowly.

Every year, there are less "must-see" technical sessions for me, personally. That was especially true this year because something like 60% of the attendees were first timers who haven't shipped their first app yet and the session schedule was designed accordingly, with quite a few beginner and intermediate level sessions. But WWDC is so much more than the sessions (and those come out on video anyway). Your first year or two attending, it's all about the sessions, but by this point for me, it's about the sessions that focus on new technologies, labs and, most importantly, a chance to catch up with people I only see once or twice a year. Well, it wasn't just new technology sessions. For obvious reasons, I also attended all of the OpenGL ES sessions this year, and they were great. They were much deeper technically than last year, and were much more focused on the OpenGL ES 2.0 programmable pipeline.

Even thought it was the same number of attendees as the last two years (5,200 attendees plus the various engineers and other staff), this year felt way busier than past years. Lines to get into sessions were often very long, and many sessions filled up. Some even filled up an overflow room. Although we've seen long lines for sessions going back to the introduction of the iPhone, it's never been like this year. Last year, for example, for the State of the Union addresses, I meandered into the overflow room ten minutes late and found a seat in one of several empty rows. This year, I sat on the floor of the overflow room for the overflow rooms. I guess that's good - people were serious about learning this year.

One negative aspect of the massive influx of newbs this year was a certain loss of etiquette. I've always been super impressed by the way my fellow WWDC geeks treat the staff and the facilities. I've never seen garbage left around or more than isolated cases of people being rude to the catering or cleaning staff. This year, unfortunately, that respect was somewhat lacking. During the keynote line, hundreds of people just left their litter laying around on the floor. It was really disgusting and I was embarrassed at that moment to be part of the group. And it didn't end with the keynote line, either, unfortunately. I saw many examples of people not bothering to pick up after themselves, or being rude to the staff. Even a few instance of people being rude to Apple engineers who were trying to help them with problems. I almost feel like I need to add a few items to my annual first timer's guide with things I had assumed any decent person would already know, like throw out your garbage, treat people with respect, and be nice to people who are trying to help you.

Let's be better next year, okay? Almost everybody I met or talked to seemed like super people, so I'm hoping this was just a one-time anomaly. I really, really would like it to be an isolated occurrence. Enough said on that topic.

Although the new iPhone 4 was the belle of the keynote ball, the real buzz at WWDC this year, as you probably know by now, was Xcode 4. For the first time in history, Apple has released the session videos to all registered developers for free, so if you haven't done so yet, you should go watch the Developer Tools State of the Union and the handful of Xcode 4 sessions. Apple's Developer Tools teams have been working really hard for quite a long time on this upcoming release, and even in the early preview state it's in, I already wish I could use it full-time. Fortunately, the Xcode team foresaw this and they made the project file format 100% backwards and forwards compatible between Xcode 3 and Xcode 4, so I can work in Xcode 4 then switch to Xcode 3 to do my ad hoc and release builds.

Honestly, one of my favorite parts of this particular WWDC was having the opportunity to buy a few rounds of drinks for some of the engineers who worked on Xcode 4. I'm not sure if those engineers have forgiven me yet, so I'm not going to call them out by name, but it's important to me during WWDC to show my appreciation to as many as I can of the people who make all the cool stuff I work with everyday. Steve Jobs gets a lot of credit, and rightly so, but he doesn't do it alone. It was really nice to see him call out some of the people who worked on iPhone 4 and iOS 4 during the keynote, but there are a lot of unsung heroes working at Apple, and most of them don't get a lot of recognition for teh awesome they bring. WWDC is the one week a year where we get to show our appreciation in person.

The User Experience lab appeared to be the biggest hit among the labs this year. Each morning, within minutes of the Moscone West doors opening up, there was a long line extending around the corner waiting for the UX lab to open. People waited in line literally for hours to get a UX review. I guess word got out this year about how good those reviews were. I know I saw several people raving about them last year. In general, I think people have really started to grok the fact that the labs represent an incredible opportunity to get questions answered by the people who really know the answers. If you're having a problem or an issue with a certain part of the system, likely you can find someone who actually works on that part of the system to answer your questions.

As for MartianCraft, we gave out all of our away team shirts pretty quickly this year. Sorry to those who wanted one but didn't get one. We way underestimated demand for the shirts and just didn't have enough with us to give them to everyone who wanted one. We'll be better prepared next year, and we're looking into making the shirts available online for anybody who's interested, but that probably won't happen until we've dug ourselves out from the hole that got created as a result of all three of us not working for a week.

Monday, June 28, 2010

WWDC First Time Guide, 2010 Edition

type='html'>Today, WWDC was announced. It's never been announced this late, so there's not a lot of time to prepare. On the other hand, there's a lot less time to wait. Because of the iPad, I'm expecting there to be a fair number of first timers at WWDC again this year, so I thought it was worth updating and re-posting my WWDC First Time Guide from last year.

Again, WWDC is different every year, so don't take anything written here as gospel, but hopefully these may help some of you.

Updated May 5th with two additional recommendations taken from the comments.

  1. Arrive on Sunday or Earlier. Registration is usually open most of the day on Sunday. You really, really want to get your badge, bag, and t-shirt on Sunday. The line for the keynote will start forming many hours before the doors to Moscone West open up on Monday. If you do not have your badge, you will almost certainly end up in an overflow room for the Keynote and may miss part of it. Even if you don't care about being in the main room, there's still a lot going on on Sunday, and you don't want to deal with the badge process on Monday.

  2. Do not lose your badge. If you lose it, you are done. You will spend your time crying on the short steps in front of Moscone West while you watch everyone else go in to get edumacated. Sure, you'll still be able to attend the after-hours and unofficial goings-on (except the Thursday night party, which is usually a blast), but you'll miss out on the really important stuff. No amount of begging or pleading will get you a replacement badge, and since they're likely to sell out, no amount of money will get you another one, either. And that would suck. Treat it like gold. When I'm not in Moscone West or somewhere else where I need the badge, I put it in my backpack, clipped to my backpack's keyper (the little hook designed to hold your keys so they don't get lost in the bottom of your bag). Yes, there have been isolated stories of people managing to convince a sympathetic conference worker to print them a new badge, but don't expect it. They're not supposed to, and most won't.

  3. Eat your fill. They will feed you two meals a day, you're on your own for dinner. Breakfast starts a half-hour before the first session, and it's probably going to be a continental breakfast - fruit, pastries, juice, coffee, donuts, toast, and those round dinner rolls that Californians think are bagels, but really aren't. If you're diabetic, need to eat gluten-free, or are an early riser, you'll probably want to eat before-hand. Lunch used to be (IIRC) a hot lunch, but two or three years ago they switched to boxed lunches. They are pretty good as far as boxed lunches go, but they are boxed lunches. A lot of people complain about them and choose to go to a nearby restaurant during the lunch break, which is pretty long - at least 90 minutes.

  4. Party hard (not that you have a choice). There are lots of official and unofficial events in the evening. There's usually a CocoaHeads meeting at the Apple Store. It fills up crazy fast, so go early if you go. It's usually competing with several other parties, but it starts earlier than most events and finishes early enough for people to go to other parties when it's done. Best bet is to follow as many iPhone and Mac devs on Twitter that you can - the unofficial gatherings happen at various places downtown, often starting with a few "seed crystal" developers stopping for a drink and tweeting their whereabouts. The unofficial, spontaneous gatherings can be really fun and a great opportunity. The parties often start before WWDC - there are usually a few on Sunday, and there have been ones as early as Saturday before. The Harlot at 111 Minna is a common place for official parties, as are Jillians in the Metreon, and the Thirsty Bear on Howard. For informal gatherings, Eddie Rickenbockers, the Chieftan, the House of Shields, and pretty much any other bar within stumbling distance of Moscone West. As we get closer, there will be lists and calendars devoted to all the events and parties. Some are invite-only, but many are first-come, first-serve. Although there's a lot of drinking going on, these are worth attending even if you don't drink. Great people, great conversations... good times.

    At some point, one or more lists will pop up to track the official parties, gatherings, meet-ups, and BOF (birds of a feather meetings - meet-ups for people interested in a particular subject).

  5. Take good notes. You are going to be drinking knowledge from a firehose there. The information will come at you fast and furious. As an attendee, you will get all the session videos on ADC on iTunes, but it takes a little while before they become available, so the things you need to know now, write down. Last year, the videos took less than a month to come out, so hopefully they will be just as fast this year, but even so, make sure you write down the information you need immediately.

  6. Buy SubEthaEdit Last year, people started taking communal notes using SubEthaEdit, an awesome collaborative note-taking tool, and it worked out really, really well. My notes from last year are ten times better than from previous years. With SubEthaEdit, you don't have to type fast enough to catch every detail. Instead, the audience works as a team and everybody gets great notes. SubEthaEdit pays for itself in one WWDC, especially considering you can see notes being taken in other sessions, not just your own. Note: I've been informed that Panic's Coda is also compatible with SubEthaEdit's colaborative note taking

  7. Labs rule. If you're having a problem, find an appropriate lab. One of the concierges at any of the labs can tell you exactly which teams and/or which Apple employees will be at which labs when. If you're having an audio problem, you can easily stalk the Core Audio team until they beat the information into your skull, for example (that example is from personal experience - those guys are awesome, by the way). It's unstructured, hands-on time with the people who write the frameworks and applications. People start remembering the labs later in the week it seems, but early on, you can often get an engineer all to yourself.

  8. Buddy up, divide and conquer There will be at least a few times when you want to be at more than one presentation at the same time. Find someone who's attending one and go to the other (Twitter is a good way to find people), then share your notes.

  9. Make sure to sleep on the plane. You won't get many other chances once you get there. Everybody is ragged by Friday, some of us even earlier. Everyone remains surprisingly polite given how sleep-deprived and/or hungover people are.

  10. Thank your hosts. The folks at Apple - the engineers and evangelists who give the presentations and staff the labs, kill themselves for months to make WWDC such a great event. So, do your mother proud and remember your manners. Say thank you when someone helps you, or even if they don't. And if you see one of them at an after hours event, it's quite alright to buy them a beer to say thanks.

  11. Remember you're under NDA. This one is hard, especially for me. We see so much exciting amazing stuff that week that it's natural to want to tweet it, blog it, or even tell the guy handing out advertisements for strip joints on the corner all about it. Don't. Everything, from morning to night except the Keynote and the Thursday night party are under NDA.

  12. Brown Bag it. Most days there are "brown bag" sessions. These are speakers not from Apple who give entertaining, enlightening, or inspiring talks at lunchtime. Check the schedule, some of them are bound to be well worth your time.

  13. Monday, Monday I don't know what to say about Monday. Last year, people started lining up at midnight the night before (again). I'm typically on East coast time and usually walk over around 4:15 to see what's going on. Two years ago, I stayed and became an insane person myself and did the whole line thing. Last year, I visited for a while and then went and had breakfast. If you're not in line early, you will still see the keynote, though you may be in an overflow room watching it on a big video screen. If you straggle too much, they may start before you get in the room (happened to me last year).

    Waiting in line is not really my thing, but you do get to talk to a lot of very cool people while waiting in line, and there is a sense of camaraderie that develops when you do something silly with other people like that. Some people probably want me to suggest what time to get in line. I have no idea. Most people will get into the main room to see the Keynote. There will be some people diverted to an overflow room, but because the number of attendees is relatively low and the Presidio (the keynote room) is so big, it's a tiny percentage who have to go to the overflow rooms (maybe the last 1,500 or so). On the other hand, you'll actually get a better view in the overflow rooms unless you get there crazy early - you'll get to watch it in real time on huge screens and you'll get to see what's happening better than the people at the back of the Presidio. So, go when you want to. If you want to get up early and go be one of the "crazy ones", cool! If you want to get up later, you'll still get to see the keynote sitting in a comfy room with other geeks.

  14. Park it once in a while There will be time between sessions, and maybe even one or two slots that have nothing you're interested in. Or, you might find yourself just too tired to take in the inner workings of some technology. In that case, there are several lounges around where you can crash in a bean bag chair, comfy chair, or moderately-comfy chair. There is good wi-fi throughout the building and crazy-fast wired connections and outlets in various spots on all floors. So, find a spot, tweet your location, and zone out for a little while or do some coding. You never know who you might end up talking with. If you move around too much, well, let's just say a moving target is harder to hit than a stationary one.

  15. Twitter is invaluable, but don't expect it to stay up during the keynote. There's really no better way to hook up with people you didn't travel with than Twitter. Two years ago, we overwhelmed twitter during the keynote. Last year it fared okay, though there were some delays and hiccups.

  16. It's okay to leave. Don't worry if a few minutes into a session you decide that you've made a horrible mistake and it's too boring/advanced/simple/etc. Just get up and leave quietly and wander to a different session. Nobody is going to be offended if you leave politely and without causing a disturbance.

  17. Bring proof of age on Thursday night. The official party is always on Thursday night, and it's always a blast. There's good food, good drink, great company, and usually a pretty good band. It was the Cake last year, Bare Naked Ladies the year before. They are pretty strict about making sure only people who are over 21 get alcohol. So, if you want to have a drink or five on Thursday, don't leave your license or passport in your hotel room.

  18. It's okay to take breaks. Your first time, you're going to be tempted to go to every session you possibly can. Somewhere around Wednesday or Thursday, though, that effort combined with lack of sleep, is going to take its toll on you. If you're too tired or overwhelmed to process information, it's okay to hole up on a couch or at a table instead of going to a session, or even to go back to your hotel (you did get a close one, right?). In fact, it's a darn good idea to map out a few "sacrificial" time slots that won't feel bad about missing just in case you need a break. You don't want to burn out and then miss something you are really interested in. And some of the best, more advanced sessions fall at the end of the week, so don't shoot your whole wad early in the week.

  19. Get a close hotel If at all possible, try and get a hotel within two block and definitely not more than five blocks away from Moscone West. Five blocks doesn't seem like a lot, but it can become quite a hassle, especially if you're South of Moscone West because you'll be climbing up a pretty decent hill in one direction.

  20. Official Evening Events In addition to the Thursday night Beer Bash, there are other official activities in the evening that are very entertaining and usually happen in the early evening before the parties really get going. The two stalwarts are the Apple Design Awards and Stump the Chumps, which is an Apple trivia game-show like event with notable tech luminaries and former Apple employees. Lots of sharp wits and deep knowledge of Apple make for some good entertainment. There used to also be a Monday night reception and cocktail hour, but if memory serves, it didn't happen last year.

  21. Take the BART If you're flying into either SFO or OAK and are staying near Moscone West (or near any BART station) there's really no reason to bother with renting a car or taking a cab from the airport. Just get off at the Powell Street station and walk up 4th street. Moscone West will be four blocks up the hill on your right.

  22. Bring a Sweatshirt or Jacket A lot of first-timers assume that it's California in the summer so it's going to be hot. Well, it could be, during the middle of the day, but look up Mark Twain's quote about San Francisco in the summer. It can be downright cold in San Francisco in the summer time, especially in the evenings and early morning. Bring a sweatshirt or light jacket, and wear layers because the temperature differential over the course of the day can be forty or fifty degrees.

  23. Sample Code Many sessions will have sample code, usually downloadable from the schedule or class descriptions web pages. The sample code will stay up for a while, but will not stay around forever, so it's a good idea to download any code samples you want as soon as you can. Edit: It looks like starting with 2009, you can get to the old source code for years you attended by logging in to ADC on iTunes.


Have more suggestions for first-timers? Add them to the comments.

Sunday, June 27, 2010

Testing 3.x Apps on Phone Running Beta OS

type='html'>#alttext#
Like many iPhone devs, I have more than one device that I use for testing. I have an iPod Touch that I usually leave at the current release version, an iPad WiFi that I leave at the current release version for the iPad, and I have an iPhone 3Gs that I keep on the bleeding edge beta release version. This way, I have a device to test and run stuff under the beta SDK and under the release SDK, and I keep both installed on my machine so I have the ability to check out and learn the next release of the SDK while still creating applications using the current release of the SDK.

Normally, this is a perfectly sufficient setup for my purposes, but today, it wasn't. I have a problem I'm trying to debug that happens when running on EDGE or on a really slow 3G connection; it never happens under WiFi. I'm doing a fix for a client that will need to go onto the App Store long before 4.0 goes GM, so I need to be working under 3.1.2.

In order to try and reproduce this problem, I have to be able to run the app on my phone because the iPod touch and iPad both only have WiFi connections so, therefore, I can't reproduce the problem there. I don't want to build against the beta SDK because this is a bug fix on an existing app, and the beta install doesn't include the current release SDK that I needs to build under, so I can't build a 3.1.2 app using the beta tools. This is probably done to discourage people from building apps for the App Store with the beta tools (which you really, really shouldn't do). The only problem is, OS versions have to match between the tools and phone, so I can't launch the GM Xcode and and debug apps on my iPhone on which I've installed the beta OS and if I run on my iPod touch, I can't reproduce the problem I need to debug because it doesn't happen under WiFi.

There's actually a solution, which is to create a symbolic link from SDK in the GM tools folder to the beta tools folder. So, in my case, I have GM tools installed at /Developer and the current beta release installed at /DevBeta. In order to compile a 3.1.3 application so I can test it on a phone that's been upgraded to 4.0, I can drop to the terminal and do this:
sudo ln -s /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS3.1.3.sdk \
/DevBeta/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS3.1.3.sdk
If I need to to run and test an application with a base SDK of 3.0 on a phone that's been upgraded to 4.0, instead I do this:
sudo ln -s /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS3.0.sdk \
/DevBeta/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS3.0.sdk
Do this with Xcode closed, then when you re-open your project with an older base SDK, it should work. All we're doing is creating a symbolic link from the beta tools directory to the appropriate SDK in the GM tool directory.

Now, be aware, this action is likely to be frowned upon by the Mothership; they excluded those SDKs for a reason. But, I haven't disclosed anything about the beta release other than its existence, which is widely known, and there are valid reasons to use the beta tools and the GM SDKs, so I though it worth sharing. Caveat emptor - do this at your own risk and don't get mad at me if it causes problems.

Another Record WWDC Sellout

type='html'>You've probably already heard that my Twitter joke of pretending WWDC had sold out was ruined today by it actually selling out - after only eight days. Last year it sold in in just about a month, of course it was announced considerably earlier last year, but I still thought the higher ticket prices this year would slow it down a touch.

Actually, apparently, I didn't. On the day WWDC was announced, I made an off-hand, joking prediction of May 6 as the sellout day. Go me! Might just be the first Apple prediction I've gotten perfectly right.

PSA: Respect the Main Thread

type='html'>I tweeted this earlier as a joke, but it's actually a very serious thing. I've had several jobs in the last year where I've been asked to look at and either suggest fixes or actually fix iPhone applications written by other developers. One thing that I've seen several times in code I've reviewed is something like this littered throughout the application:

[NSThreadsleepForTimeInterval:0.3];

This code causes the thread it's called from the do nothing for the specified length of time. Then I'll look through the code for some indication that the application is spawning threads. So far, in every case, the app hadn't spawned any threads, explicitly, or implicitly using NSOperationQueue. If you haven't detached any threads, and you call sleepForTimeInterval:, you are sleeping the application's main thread, and that is a very bad thing to do.

Usually, I see these sleepForTimeInterval: calls in applications that do some kind of asynchronous network communication. My guess is that the developers who wrote these applications came from Java, .Net, or some other language where network communication is commonly handled on a non-main thread. In those languages, it's not uncommon to see code that puts the network worker thread to sleep or into a loop to make it wait for a response and account for any potential lag time. It's not a great approach, but in these environments, it generally works.

In Cocoa and Cocoa Touch, however, unless you specifically spawn a thread and register your network communications with the spawned thread's run loop, your network communications using CFNetwork as well as any networking done using the built-in networking functionality that exists in many Cocoa classes all happens on the main thread.

Another related problem that I sometimes see is something like this:

- (IBAction)soSomething
{
NSData *data = [NSDatadataWithContentsOfURL:[NSURLurlWithString:@"http://foo.com/reallybigdatafile"]];
while(1)
{
// parse really big data file and break when done
}

}

This code has a few really bad things going on. It's important to keep in mind that IBAction methods always fire on the main thread. So, the first problem is the call to dataWithContentsOfURL:. This is what's called a blocking network operation, which means that calling this method will prevent any further code on this thread from executing until all the data has been received because the method will not return until that has happened. The same thing is true for all of the convenience constructors that contain withContentsOfURL: in their name. While you can probably get away with very limited use of these methods for very small pieces of data, you really should be only using asynchronous network calls in your apps. You can't predict how long network communications will take, even with tiny pieces of data. If the radios are powered down, EDGE connections can take several seconds to re-establish, which is long enough that your user will probably notice the freeze.

The second problem with this thread is the while loop. Small loops are often okay in action methods, but those that could potentially be large or where the length of the loop is simply impossible to know (which is typically the case with while (1) or while(TRUE) loops), you don't want to be doing the processing on the main thread.

Why All This is Bad


If you sleep the main thread, quite literally nothing can happen in your app except for background thread execution. If you haven't spawned any background threads, then pretty much nothing can happen at all. Your user interface will freeze up and your user won't be able to use the application. Buttons won't highlight when tapped and animations will freeze in place. But it's even worse than the interface freezing (which is certainly bad enough), all event processing and network communications will freeze up also. Anything that is handled by your application's main event loop simply doesn't happen while the main thread is asleep.

I have yet to see a situation where calling sleepForTimeInterval: on the main thread is a good idea.

If you perform a long-running operation on the main thread, your code will monopolize the main thread and the end result will be essentially the same as sleeping the main thread while your loop runs. No user interaction, no network communications, no animation.

If you want a good introduction on how to get stuff off your main thread, check out Dave Dribbin's blog post on concurrent operations. More iPhone 3 Development also devotes a full chapter to different types of concurrency.

Saturday, June 26, 2010

HTML Party List

type='html'>Geoff Pado has created a small web app for keeping track of the WWDC parties this year. If you've got a party, social hour, birds-of-a-feather gathering, meet-up, or anything else you'd like listed, tweet them to Geoff.

Edit: Instead of tweeting new parties, there's now an submission form you should use instead.

In app settings

Type = 'html' > I have not tried it yet, but This is a great idea. It is a framework that your application reads settings bundle and presents applications settings within the application with the same user interface settings applications used. This is one of the things you look at that and go... "Do why not Apple to start it in this way".

Custom Alert Views

type='html'>Quite some time ago, I posted a way to accept text input using an alert view. The problem with that technique is that it relies on one of Apple's private APIs. I've had many people ask me how to use that technique without getting rejected in the review process. The short answer is: you can't.

What you can do, however, is create your own class that simulates the behavior of UIAlertView. You can find a sample implementation of the technique discussed in this post by downloading this project. In this simple example, we're going to create an alert view that lets the user enter a value into a single text field. The same technique can be used to present any view that you can build in Interface Builder. From a programming perspective, you will not get rejected for using private APIs if you use this technique, but be aware that you can still get rejected for HIG violations, so make sure you're familiar with the HIG.

There are many ways to mimic the behavior of the UIAlertView. One of the most common ways I've seen is to simply design an alert view in the nib of the view controller that needs to present it. This works, but it's a bit messy and pretty much completely non-reusable.

A better approach is to design the alert view as its own view controller and nib pair. We can even model it after UIAlertView's fire-and-forget approach so that the calling code can look exactly like code to create and display a UIAlertView. Before we get started, we need a few resources.

You've probably noticed that when you use an alert view, the active view grays out a bit. I don't know for sure how Apple has accomplished this, but we can simulate the behavior using a PNG image with a circular gradient that goes from 60% opaque black to 40% opaque black:

behind_alert_view.png

We also need a background image for the alert view. Here's one I hacked out in Pixelmatoralert_background.png

For your own alerts, you might need to resize or turn this into a stretchable image. For simplicity's sake, I just made it the size I want it. We also need buttons. Again, in a real example, you might want to turn these into stretchable images, but I just kept things simple by making the image the size I wanted it:

alert_button.png

The next thing we need is some animation code. We could, of course, create the CAAnimation instances right in our class, but I'm a big fan of both code reuse and Objective-C categories, so instead of doing that, I've created a category on UIView that will handle the two animations we need. One animation "pops in" a view and will be used to show the alert view, the other fades in a view and will be used for the gray background image. The two animations happen at the same time so that when the alert has fully popped into view, the background view is grayed out.

The keyframe timings on the "pop in" animation code are not quite a 100% match for Apple's animation, so if anybody wants to tweak the keyframe values to make a closer match, I'd be happy to update the code with the "correct" values. Here is the category I created to hold the animations:

UIView-AlertAnimations.h
#import <Foundation/Foundation.h>


@interface UIView(AlertAnimations)
- (void)doPopInAnimation;
- (void)doPopInAnimationWithDelegate:(id)animationDelegate;
- (void)doFadeInAnimation;
- (void)doFadeInAnimationWithDelegate:(id)animationDelegate;
@end



UIView-AlertAnimations.m
#import "UIView-AlertAnimations.h"
#import <QuartzCore/QuartzCore.h>

#define kAnimationDuration 0.2555

@implementation UIView(AlertAnimations)
- (void)doPopInAnimation
{
[self doPopInAnimationWithDelegate:nil];
}

- (void)doPopInAnimationWithDelegate:(id)animationDelegate
{
CALayer *viewLayer = self.layer;
CAKeyframeAnimation* popInAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale"];

popInAnimation.duration = kAnimationDuration;
popInAnimation.values = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0.6],
[NSNumber numberWithFloat:1.1],
[NSNumber numberWithFloat:.9],
[NSNumber numberWithFloat:1],
nil
]
;
popInAnimation.keyTimes = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0.0],
[NSNumber numberWithFloat:0.6],
[NSNumber numberWithFloat:0.8],
[NSNumber numberWithFloat:1.0],
nil
]
;
popInAnimation.delegate = animationDelegate;

[viewLayer addAnimation:popInAnimation forKey:@"transform.scale"];
}

- (void)doFadeInAnimation
{
[self doFadeInAnimationWithDelegate:nil];
}

- (void)doFadeInAnimationWithDelegate:(id)animationDelegate
{
CALayer *viewLayer = self.layer;
CABasicAnimation *fadeInAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];
fadeInAnimation.fromValue = [NSNumber numberWithFloat:0.0];
fadeInAnimation.toValue = [NSNumber numberWithFloat:1.0];
fadeInAnimation.duration = kAnimationDuration;
fadeInAnimation.delegate = animationDelegate;
[viewLayer addAnimation:fadeInAnimation forKey:@"opacity"];
}

@end


As you can see, I've provided two versions of each animation, one that accepts an animation delegate and one that doesn't. This allows the calling code to set an animation delegate so it can be notified of things like when the animation finishes. We'll use a delegate for one of our animations, but we might as well have the option with both.

The next thing we need to do is create the view controller header, implementation, and nib files. In my sample code, I've named the class CustomAlertView. It may seem odd that I giving a view controller class a name that makes it sound like a view instead of something like CustomAlertViewController. You can feel free to name yours however you wish, but this controller class will actually mimic the behavior of UIAlertView and I wanted the name to reflect that. I debated with myself over the name for a bit, but ultimately decided that CustomAlertView felt better since the name would provide a clue as to how to use it. If it feels dirty to you to "lie" in the class name, then by all means, name yours differently.

We're going to need some action methods and some outlets so that our controller and nib file can interact. One outlet will be for the background image, another for the alert view itself, and one for the text field. The latter is needed so we can tell the text field to accept and resign first responder status. We'll use a single action method for both of the buttons on the alert, and we'll use the button's tag value to differentiate between the two buttons.

Because we're implementing fire-and-forget, we also need to define a protocol containing the methods the delegate can implement. Since the alert is designed to accept input, I've made the delegate method used to receive the input @required, but the other method (called only when cancelled) @optional. The enum here is just to make code more readable when it comes to dealing with the button tag values.

CustomAlertView.h
#import <UIKit/UIKit.h>

enum
{
CustomAlertViewButtonTagOk = 1000,
CustomAlertViewButtonTagCancel
}
;

@class CustomAlertView;

@protocol CustomAlertViewDelegate
@required
- (void) CustomAlertView:(CustomAlertView *)alert wasDismissedWithValue:(NSString *)value;

@optional
- (void) customAlertViewWasCancelled:(CustomAlertView *)alert;
@end



@interface CustomAlertView : UIViewController <UITextFieldDelegate>
{
UIView *alertView;
UIView *backgroundView;
UITextField *inputField;

id<NSObject, CustomAlertViewDelegate> delegate;
}

@property (nonatomic, retain) IBOutlet UIView *alertView;
@property (nonatomic, retain) IBOutlet UIView *backgroundView;
@property (nonatomic, retain) IBOutlet UITextField *inputField;

@property (nonatomic, assign) IBOutlet id<CustomAlertViewDelegate, NSObject> delegate;
- (IBAction)show;
- (IBAction)dismiss:(id)sender;
@end


Once the header file is completed and saved, we can skip over to Interface Builder and create our interface. I won't walk you through the process of building the interface, but I'll point out the important things. Here's what the view will looks like in Interface Builder:

Screen shot 2010-05-17 at 12.42.51 PM.png

Some important things:
  • The content view needs to be NOT opaque and its background color should be set to a white with 0% opacity. The view's alpha should be 1.0. Alpha is inherited by subviews, background color is not, so by making it transparent using background color, we'll be able to see all of the subviews. If we had set alpha to 0.0 instead, we wouldn't be able see the alert view;
  • The background view is a UIImageView that is the same size as the content view. Its autosize attributes are set so that it resizes with the content view and it is connected to the backgroundView outlet. It needs to not be opaque and its alpha needs to be set to 1.0. Even though we will be animating the alpha, we want the nib to reflect the final value;
  • All of the UI elements that make up the actual alert are all subviews of a single instance of UIView whose background color is also set to white with 0% opacity and is NOT opaque so that it is invisible. This view is used just to group the elements so they can be animated together and is what the alertView outlet will point to;
  • The alert view is not centered. In this case, we want it centered in the space remaining above the keyboard;
  • The OK button has its tag set to 1,000 in the attribute inspector. The Cancel button has its tag set to 1,001. These numbers match the values in the enum we created in the header file
  • File's Owner is the delegate of the text field. This allows the controller to be notified when the user hits the return key on the keyboard


Once the interface is created, all that's left to do is implement the controller class. Here is the implementation; I'll explain what's going on in a moment:

CustomAlertView.m
#import "CustomAlertView.h"
#import "UIView-AlertAnimations.h"
#import <QuartzCore/QuartzCore.h>

@interface CustomAlertView()
- (void)alertDidFadeOut;
@end


@implementation CustomAlertView
@synthesize alertView;
@synthesize backgroundView;
@synthesize inputField;
@synthesize delegate;
#pragma mark -
#pragma mark IBActions
- (IBAction)show
{
// Retaining self is odd, but we do it to make this "fire and forget"
[self retain];

// We need to add it to the window, which we can get from the delegate
id appDelegate = [[UIApplication sharedApplication] delegate];
UIWindow *window = [appDelegate window];
[window addSubview:self.view];

// Make sure the alert covers the whole window
self.view.frame = window.frame;
self.view.center = window.center;

// "Pop in" animation for alert
[alertView doPopInAnimationWithDelegate:self];

// "Fade in" animation for background
[backgroundView doFadeInAnimation];
}

- (IBAction)dismiss:(id)sender
{
[inputField resignFirstResponder];
[UIView beginAnimations:nil context:nil];
self.view.alpha = 0.0;
[UIView commitAnimations];

[self performSelector:@selector(alertDidFadeOut) withObject:nil afterDelay:0.5];



if (sender == self || [sender tag] == CustomAlertViewButtonTagOk)
[delegate CustomAlertView:self wasDismissedWithValue:inputField.text];
else
{
if ([delegate respondsToSelector:@selector(customAlertViewWasCancelled:)])
[delegate customAlertViewWasCancelled:self];
}

}

#pragma mark -
- (void)viewDidUnload
{
[super viewDidUnload];
self.alertView = nil;
self.backgroundView = nil;
self.inputField = nil;
}

- (void)dealloc
{
[alertView release];
[backgroundView release];
[inputField release];
[super dealloc];
}

#pragma mark -
#pragma mark Private Methods
- (void)alertDidFadeOut
{
[self.view removeFromSuperview];
[self autorelease];
}

#pragma mark -
#pragma mark CAAnimation Delegate Methods
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
{
[self.inputField becomeFirstResponder];
}

#pragma mark -
#pragma mark Text Field Delegate Methods
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
[self dismiss:self];
return YES;
}

@end



So, what're we doing here? First, we start by importing the category with our alert view animations and also QuartzCore.h which gives us access to all the datatypes used in Core Animation.

#import "CustomAlertView.h"
#import "UIView-AlertAnimations.h"
#import <QuartzCore/QuartzCore.h>

Next, we declare a class extension with a single method. By putting this method here, we can use it anywhere in our class without getting warnings from the compiler yet we do not advertise the existence of this method to the world. This is, essentially, a private method. In a dynamic language like Objective-C, there are no truly private methods, but since the method is not declared in the header file, that's our way of saying "this is ours, don't touch". This method, which you'll see in a moment, will be called after the alert has been dismissed to remove it from its superview. We don't want to remove it until after the fade-out animation has finished, which is why we've declared a separate method.

@interface CustomAlertView()
- (void)alertDidFadeOut;
@end


After we synthesize our properties, the first method we write is show. This is the method that gets called to, well… show the alert. I matched the method name used in UIAlertView and also made it an IBAction so that it can be triggered directly inside a nib file.

The weirdest part of this method is that it actually retains self. This is something you're generally not going to want to do. Since we've implemented our alert view as a view controller instead of a UIView subclass like UIAlertView, we need to cheat a little because a view controller is not retained by anything by virtue of its view being in the view hierarchy. This isn't wrong - we're going to bookend our retain with a release (well, actually, an autorelease) so no memory will leak, but it is unusual and not something you're going to want to use in very many places. When you retain self, you need to take a long hard look at your code and make sure you have a darn good reason for doing it. In this instance, we do.

After retaining, we grab a reference to the window by way of the application delegate and add our view to the window, matching its frame. Then we call the two animation methods we created earlier to fade in the image with the circular gradient and "pop" in the alert view:

- (IBAction)show
{
// Retaining self is odd, but we do it to make this "fire and forget"
[self retain];

// We need to add it to the window, which we can get from the delegate
id appDelegate = [[UIApplication sharedApplication] delegate];
UIWindow *window = [appDelegate window];
[window addSubview:self.view];

// Make sure the alert covers the whole window
self.view.frame = window.frame;
self.view.center = window.center;

// "Pop in" animation for alert
[alertView doPopInAnimationWithDelegate:self];

// "Fade in" animation for background
[backgroundView doFadeInAnimation];
}

The next action method we write is the one that gets called by the two buttons on the alert. Regardless of which button was pushed, we want the text field to resign first responder status so that the keyboard disappears, and we want the alert to fade away. We're going to use implicit animations this time and then use performSelector:withObject:afterDelay: to trigger our private method that will remove the view from its superview. After that, we check sender's tag value to see which delegate method to notify.

- (IBAction)dismiss:(id)sender
{
[inputField resignFirstResponder];
[UIView beginAnimations:nil context:nil];
self.view.alpha = 0.0;
[UIView commitAnimations];

[self performSelector:@selector(alertDidFadeOut) withObject:nil afterDelay:0.5];

if (sender == self || [sender tag] == CustomAlertViewButtonTagOk)
[delegate CustomAlertView:self wasDismissedWithValue:inputField.text];
else
{
if ([delegate respondsToSelector:@selector(customAlertViewWasCancelled:)])
[delegate customAlertViewWasCancelled:self];
}

}

The viewDidUnload and dealloc are bog standard, so there's no point in discussing them. The next method after those is our "private" method. It does nothing more than remove the now invisible alert view from the view hierarchy and autoreleases self so that the view controller will be released at the end of the current run loop iteration. We don't want to use release because we really don't want the object disappearing while one of its method is executing:

- (void)alertDidFadeOut
{
[self.view removeFromSuperview];
[self autorelease];
}

Earlier, when we created the "pop in" animation, we specified self as the delegate. The next method we implement is called when the "pop in" animation completes by virtue of that fact. All we do here is make sure the keyboard is shown to the user and associated with our text field. We do all that simply by making the text field the first responder:

- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
{
[self.inputField becomeFirstResponder];
}

Finally, we implement one of the text field delegate methods so that when the user presses the return key on the keyboard, it dismisses the dialog.

- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
[self dismiss:self];
return YES;
}

At this point, we're done. We can now use this custom alert view exactly the same way we use UIAlertView:

 CustomAlertView *alert = [[CustomAlertView alloc]init];
alert.delegate = self;
[alert show];
[alert release];


As I stated earlier, you can use this same technique to present anything you can build in Interface Builder, and the result will be a highly-reusable alert object.