Blog Archives

Table View Cells Redux

Quite a while ago, I posted about Apple’s recommended way of doing custom table view cells in Interface Builder. The code from that post has been available for 9 months and today, for the first time, somebody pointed out to me that the attached project didn’t reuse dequeued cells because I forgot to type the cell’s identifier in Interface Builder.

Which is the weakness with Apple’s recommended approach. It’s a real Achille’s heel. It’s really, really easy to forget that step, and the code works perfectly fine if you do forget, you’re just eating memory and getting poorer performance than you should. Unless you profile your apps or test with very large data sets, you could very well ship your app like this and not even realize it. You never want your customers to discover these things before you.

Now, I didn’t even know I had made this mistake until today, but I’ve known it was a potential problem for quite some time, which is why, in my contract work, I’ve started using a modified version of the technique. My earlier mistake now gives me a good excuse to post that modification.

Mostly, it’s the same technique as I discussed before, only I start out by defining a constant for the identifier. I actually create a header file in my Xcode projects ConstantsAndMacros.h in my project, which I add to my pre-compiled header file. This means that any constants and any macros I put in ConstantsAndMacros.h will be available to all my source code files in my project without having to manually import them. For a simple project, that file might look something like this:

#define TABLE_CELL_IDENTIFIER                   @"Table Cell Identifier"#define NSStubLog()                             NSLog(@"%s", __PRETTY_FUNCTION__)¹

Once I have that file, I add it to the .pch file in the Other Sources folder:

#ifdef __OBJC__    #import <Foundation/Foundation.h>    #import <UIKit/UIKit.h>    #import "ConstantsAndMacros.h"#endif

Now that constant is available project wide². Then, in my UITableViewCell subclass, I override the reuseIdentifier method and add a class method with the same name, like this:

+ (NSString *)reuseIdentifier{    return (NSString *)TABLE_CELL_IDENTIFIER;}- (NSString *)reuseIdentifier{    return [[self class] reuseIdentifier];}

By doing this, the system will ignore any identifier I set in Interface Builder, or any value I set in code using the setBundleIdentifier: mutator method. For instances of this particular class, it will always use the same identifier. By creating the class method, I have access to that identifier even if I don’t yet have an instance of the class.

For all the other steps in using custom table view cells loaded from a nib, the process is the same as the previous tutorial and it works great. Here’s an example tableView:cellForRowAtIndexPath: method using this technique:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {    TestCell *cell = (TestCell *)[tableView dequeueReusableCellWithIdentifier:[TestCell reuseIdentifier]];    if (cell == nil)     {        NSLog(@"Loading new cell");        [[NSBundle mainBundle] loadNibNamed:@"TestCell" owner:self options:nil];        cell = loadCell;        self.loadCell = nil;    }    cell.cellLabel.text = [NSString stringWithFormat:@"Row %d", [indexPath row]];    return cell;}

Notice the NSLog() statement? I can open my table view cell in Interface Builder and set any identifier I want, or set no identifier at all, and it will still re-use table view cells. Run the app, and no matter what you do in Interface Builder, you’ll only see a handful of rows loaded from the nib file. After the initial loads, it will just keep reusing the same cell instances over and over. This is far less fragile than having to make sure the identifier in IB and the one in your code match.

There might be times when you don’t want to set it up like this – when you need to have multiple identifiers for the same table view class, but those situations will be exceedingly rare. In most practical situations where you are subclassing UITableViewCell, you will want a single identifier for all instances of that class. What you normally won’t want is the fragility of having to make sure the value in your code matches the one in IB exactly, especially given that there are no obvious signs that you’ve forgotten to do it.


1 This macro just logs the name of the method of function that it’s placed in when that method is called. I use it whenever I stub out an IBAction method to make sure my connections are all made correctly, hence the name, but it’s useful for debugging as well.

2 If you will only use a table view cell in a single controller, you probably don’t want it here. In general, I try to create table view cells to be generic enough to be used in more than one controller. Sometimes that’s not possible or practical, and in those cases, #define your identifier in the table view controller header instead.

You can find a sample implementation project right here/

©2008-2010 Jeff LaMarche.
iphonedevelopment.blogspot.com

Converting iPhone Apps to Universal Apps

Well, the NDA has finally lifted, so we can start talking about iPhone SDK 3.2 and the iPad. The logical starting place seemed to be how to convert your existing applications into a “Universal App” that runs natively both on the iPad and the iPhone/iPod touch. Now, a lot of you have likely already had to figure this stuff out so you could get your updated app on the store today, but for those who didn’t go the early adopter route, let’s take a few minutes to look at the process. It’s pretty straightforward but there are a few gotchas.

Note: There are some additional things you should know, so read this post also before tackling your update.

Targeting All Devices

The first thing you have to do is identify that you want to build your existing application as a universal application. For this article, I’m using the Xcode project from OpenGL ES Particle Generator Application, but I’ll try to keep the information general. Note: the following step is not needed if you use Xcode’s Update Project Target for iPad option talked about here.

Bring up your Project Info window in Xcode by either double-clicking on your project’s root node in the Groups & Files pane or selecting Edit Project Settings from the Project menu and then navigate to the Build tab. Now, the change we’re about to make needs to be made to all configurations, so make sure that the Configuration popup menu is set to All Configurations, otherwise you’ll only make the change on one configuration.

We need to change a setting called Target Device Family, so type Target into the search bar, or just search for that entry manually (it’ll be under the Deployment heading). Right now, it should look like this:

Screen shot 2010-04-01 at 10.09.58 AM.png

See how it says iPhone? Yeah, you know what to do. Click on it and change it so it reads iPhone/iPad, like so:

select_target.png

Good! now you’re done, right? Most likely, no.

Auditing for Hardcoded Sizes

The next thing you’re going to want to do is audit your application to see if you hard-coded the screen size anywhere in your application. You shouldn’t have hardcoded those values, but let’s face it, we’ve all done it. A Project Find (??F) for 320 and 480 and that should turn up any of those hardcoded values. In the Particle Generator code, I did it in only one place, in code that creates a UIImage of the OpenGL view. The line of code where I did it looks like this:

    CGImageRef imageRef = CGImageCreate(320, 480, bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpaceRef, bitmapInfo, provider, NULL, NO, renderingIntent);

Usually, the fix for this will be obvious. Instead of hardcoding, you want to pull the width and height from the OpenGL view. The code where I did it actually exists on the GLView class, so I can fix it like so:

    CGImageRef imageRef = CGImageCreate(self.frame.size.width, self.frame.size.height, bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpaceRef, bitmapInfo, provider, NULL, NO, renderingIntent);

But, what if what you’ve hardcoded is the actual size of the view? Then you need to pull the size from the main screen instead of the view. That’s easy enough to do.

    UIScreen *screen = [UIScreen mainScreen];    [myView setFrame:[screen applicationFrame]];

Dealing with Different Window Sizes

Most likely your application’s one instance of UIWindow is contained in your MainWindow.xib file that gets loaded automatically. Most likely, that Window is hardcoded to 320×480. Now, you might think that you can just go into Interface Builder and set the autosize attributes for the window and it will get resized for you at launch. You would be wrong. There is no automatic check to make sure your window is the right size.

You have to make sure that the window is the right size for the device you’re running on. There are, basically, two ways of doing that. If your application is such that you just need to resize the window and your autosize attributes will take care of making everything look nice, then you can just handle this programmatically in applicationDidFinishLaunching: by setting the window’s size to the size of the screen, less the status bar andy any other objects controlled by the iPhone OS (this is known as the Application Frame). Doing this looks almost exactly like setting the size of the view above:

    CGRect  rect = [[UIScreen mainScreen] bounds];    [window setFrame:rect];

Now, this is actually a good approach for the Particles application because it has one full-screen view. However, the iPad and the iPhone are really different devices, and there are several UI components available on the iPad that aren’t available (at least yet) on the iPhone, such as split views and pop up views. For many applications, especially complex applications using a lot of UIKit views and controls, you’re probably going to want to provide completely different NIB file based on which device on which the code is running.

Info.Plist Device-Specific Entries

The one really important nib file in every iPhone application is, of course, MainWindow.xib, and there has to be a way to tell your application to use a different MainWindow.xib for different devices. In fact, there is. For each key that Info.plist supports, such NSMainNibFile, which is used to specify the name of the application’s main nib file, you can now specify device-specific entries. If you provide a device-specific entry for the device the application is currently running on, it will use the device-specific value, otherwise it will just use the normal value.

Device-specific keys are exactly the same as the original or default key except the key name is followed by a tilde (~) and then the name of the device in all-lowercase letters. So, to tell our application to load a different nib file for the iPad, we can add a key called NSMainNibFile~ipad and then specify the name of the nib file to use when launching on an iPad. For the iPhone and iPod touch, it will continue to use the default value, MainWindow.xib, but for the iPad, it will use the nib file you’ve specified in the new, device-specific key.

You can add a new version of MainWindow.xib to your project by selecting the Resources group and choosing Add New File from the File menu. From the New File Assistant, select User Interface from under the iPhone OS, then select Application XIB, and make sure you select the right device in the Product drop-down.

Screen shot 2010-04-01 at 10.43.29 AM.png

Make sure you remember to connect all the outlets and actions in this new nib to the same outlets and actions you used in the other nib. Remember, only one of the application nibs will be loaded, so there’s no conflict.

For any key in the Info.plist file, you can use this same technique to override the default value with a device specific. You could, for example, have the iPhone version start in Portrait and the iPad version start in landscape, like so:

    ...    <key>UIInterfaceOrientation</key>    <string>UIInterfaceOrientationPortrait</string>    <key>UIInterfaceOrientation~ipad</key>    <string>UIInterfaceOrientationLandscapeLeft</string>    ...

Programmatically Determining Device

If you have code that needs to vary depending on whether it’s running on the iPad or iPhone/iPod touch, Apple has provided a new macro called UI_USER_INTERFACE_IDIOM() that will tell you that. There are currently two values defined, UIUserInterfaceIdiomPhone and UIUserInterfaceIdiomPad, and this macro will return the value that corresponds to the device being run. So, for example, if you needed to push a view controller onto the navigation stack, but wanted a different nib used for the iPad than the iPhone, you might do this:

    MyController *controller = nil;

    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)        controller = [[MyController alloc] initWithNibName:@"MyiPadNib" bundle:nil];    else        controller = [[MyController alloc] initWithNibName:@"MyiPodNib" bundle:nil];

    [self.navigationController pushViewController:controller animated:YES];    [controller release];

If you need finer-grain control, and need to know exactly which device, there’s no official, supported way to determine the exact device. The vast majority of the time you you think you need to know the device, you don’t actually need to know the device, you just need to know which features are supported. Even though you can find code around the web that will determine the device based on UIDevice, you really shouldn’t base your logic on that because such code can be fragile since you don’t know what future devices will exist, or what features they will have.

In cases like the Image Picker, Apple provides a way to determine which features are available on your device, such as whether there’s a camera, and whether that camera supports video. When Apple hasn’t provide a specific check or test, what you can do is use NSClassFromString(), which (as is probably obvious from the name) creates a Class instance based on the name of a class contained in a string. If this returns nil, then you know the class you’re asking about isn’t available. You can wrap your code that uses classes that aren’t available everywhere in these checks and make code that works correctly on all devices, and will continue to do so in the future (for the most part – it’s never possible to 100% future-proof code). Here’s an example of checking for the existence of the UISplitViewController, which is a new class only available on the iPad:

    Class splitViewController = NSClassFromString(@"UISplitViewController");    if (splitViewController)    {       UISplitViewController* mySplitViewController = [[splitVCClass alloc] init];       // ... configure, use, then release    }

You can do something similar with C functions by checking if the function is NULL For example, one of the frameworks added with iPhone SDK 3.2 is CoreText.framework. If we wanted to use the function CTFontCreateWithName() to create a new font using that framework, we could wrap the logic in an interface idiom check, like above, or we could just check to see if the function we want to use exists by seeing if the symbol CTFontCreateWithName is NULL at runtime, like so:

    if (CTFontCreateWithName != NULL)        CTFontFontRef myFont = CTFontCreateWithName(@"Comic Sans", 14.0, NULL);

Go, Go, Gadget iPad, Go!

Well, that pretty much covers the basics you’ll need to convert your existing iPhone apps to Universal Apps. The more complex your app, the more likely you’ll want to consider doing separate iPad and iPhone applications. I’ll show how to add another target to your Xcode project so you can generate two applications from the same project in a future post. For many apps, however, this should be enough to get you porting away, so port away!

©2008-2010 Jeff LaMarche.
iphonedevelopment.blogspot.com

Improved Irregular Shape UIButton

I took some of the feedback and improved the UIButton subclass from my last post. I implemented a cache for the alpha data and also incorporates changes based on Alfons Hoogervorst’s modifications to my original UIImage category.

You can find the new and improved version of the irregular shaped UIButton code here.

©2008-2010 Jeff LaMarche.
iphonedevelopment.blogspot.com

Irregularly Shaped UIButtons

Note: There is an improved version of the code from this blog post right here.

You probably know that UIButton allows you to select an image or background image with alpha, and it will respect the alpha. For example, if I create four images that look like this:

Screen shot 2010-03-26 at 1.02.47 PM.png

I can then use create custom buttons in Interface Builder using these images, and whatever is behind the transparent parts of the button will show through (assuming the button is not marked opaque. However, UIButton‘s hit-testing doesn’t take the transparency into account, which means if you overlap these buttons in Interface Builder so they look like this, for example:

Screen shot 2010-03-26 at 1.04.17 PM.png

If you click here:

pointat.png

The default hit-testing is going to result in the green diamond button getting pressed, not the blue one. While this might be what you want some of the time, typically this won’t be the behavior want. So, how do you get it to work like that? It’s actually pretty easy, you just need to subclass UIButton and override the hit testing method.

But, first, we need a way to determine if a given point on an image is transparent. Unfortunately, UIImage is an opaque type without a mechanism to give us easy access to the bitmap data the way NSBitmapRepresentation does for NSImages in Cocoa. But, every UIImage instance does have a property called CGImage that gives us access to the underlying image data, and Apple has very nicely published a tech note telling how to get access to the underlying bitmap data from a CGImageRef.

Using the information in that technote, we can easily craft a category on UIImage with a method that takes a CGPoint as an argument and returns either YES or NO depending on whether the alpha value that corresponds to that point is transparent (0).

UIImage-Alpha.h

#import <UIKit/UIKit.h>

@interface UIImage(Alpha)- (NSData *)ARGBData;- (BOOL)isPointTransparent:(CGPoint)point;@end

UIImage-Alpha.m

CGContextRef CreateARGBBitmapContext (CGImageRef inImage){    CGContextRef    context = NULL;    CGColorSpaceRef colorSpace;    void *          bitmapData;    int             bitmapByteCount;    int             bitmapBytesPerRow;

    size_t pixelsWide = CGImageGetWidth(inImage);    size_t pixelsHigh = CGImageGetHeight(inImage);    bitmapBytesPerRow   = (pixelsWide * 4);    bitmapByteCount     = (bitmapBytesPerRow * pixelsHigh);

    colorSpace = CGColorSpaceCreateDeviceRGB();    if (colorSpace == NULL)        return nil;

    bitmapData = malloc( bitmapByteCount );    if (bitmapData == NULL)     {        CGColorSpaceRelease( colorSpace );        return nil;    }    context = CGBitmapContextCreate (bitmapData,                                     pixelsWide,                                     pixelsHigh,                                     8,                                     bitmapBytesPerRow,                                     colorSpace,                                     kCGImageAlphaPremultipliedFirst);    if (context == NULL)    {        free (bitmapData);        fprintf (stderr, "Context not created!");    }    CGColorSpaceRelease( colorSpace );

    return context;}

@implementation UIImage(Alpha)- (NSData *)ARGBData{    CGContextRef cgctx = CreateARGBBitmapContext(self.CGImage);    if (cgctx == NULL)         return nil;

    size_t w = CGImageGetWidth(self.CGImage);    size_t h = CGImageGetHeight(self.CGImage);    CGRect rect = {{0,0},{w,h}};     CGContextDrawImage(cgctx, rect, self.CGImage); 

    void *data = CGBitmapContextGetData (cgctx);    CGContextRelease(cgctx);     if (!data)        return nil;

    size_t dataSize = 4 * w * h; // ARGB = 4 8-bit components    return [NSData dataWithBytes:data length:dataSize];}    - (BOOL)isPointTransparent:(CGPoint)point{    NSData *rawData = [self ARGBData];  // See about caching this    if (rawData == nil)        return NO;

    size_t bpp = 4;    size_t bpr = self.size.width * 4;

    NSUInteger index = point.x * bpp + (point.y * bpr);    char *rawDataBytes = (char *)[rawData bytes];

    return rawDataBytes[index] == 0;

}@end

Once we have the ability to tell if a particular point on an image is transparent, we can then create our own subclass of UIButton and override the hitTest:withEvent: method to do a slightly more sophisticated hit test than UIButton‘s. The way this works is that we need to return an instance of UIView. If the point is not a hit on this view or one of its subclasses, we return nil.. If it’s a hit on a subview, we return the subview that was hit, and if it’s a hit on this view, we return self.

However, we can simplify this a little because, although UIButton, inherits from UIView and can technically have subviews, it is exceedingly uncommon to do so and, in fact, Interface Builder won’t allow it. So, we don’t have to worry about subviews in our implementation unless we’re doing something really unusual. Here’s a simple subclass of UIButton that does hit-testing based on the alpha channel of the image or background image of the button, but assumes there are no subviews.

IrregularShapedButton.h

#import <UIKit/UIKit.h>

@interface IrregularShapedButton : UIButton {

}

@end

IrregularShapedButton.m

#import "IrregularShapedButton.h"#import "UIImage-Alpha.h"

@implementation IrregularShapedButton- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{    if (!CGRectContainsPoint([self bounds], point))        return nil;    else     {        UIImage *displayedImage = [self imageForState:[self state]];        if (displayedImage == nil) // No image found, try for background image            displayedImage = [self backgroundImageForState:[self state]];        if (displayedImage == nil) // No image could be found, fall back to             return self;        BOOL isTransparent = [displayedImage isPointTransparent:point];        if (isTransparent)            return nil;

    }

    return self;}@end

If we change the class of the four image buttons in Interface Builder from UIImage to IrregularShapedButton, they will work as expected. You can try the code out by downloading the Xcode project. Improvements and bug-fixes are welcome.

Curiously, the documentation for hitTest:withEvent: in UIView says This method ignores views that are hidden, that have disabled user interaction, or have an alpha level less than 0.1.. In my testing, this is actually not true, though I am unsure whether it’s a documentation bug or an implementation bug.

Update: My Google-Fu failed me. I did search for existing implementations and tutorials about this subject before I wrote the posting (I hate reinventing the wheel), but I failed to find Ole Begemann’s implementation of this from a few months ago. It’s worth checking out his implementation to see different approaches to solving the same problem. There’s also some discussion in the comments about the differences in our implementations that may be of interest if you like knowing the nitty-gritty details. Plus, his diamonds are prettier than mine.

Update 2: Alfons Hoogervorst tweaked the code andshowed how you could reduce the overhead by creating an alpha-only context.

©2008-2010 Jeff LaMarche.
iphonedevelopment.blogspot.com

NSTimer: The Poor Man’s Threading – Code Snapshot

Introduction

Hey guys. So usually the posts we put up here involve screencasts and presentations, but we are going to start also posting small less time consuming pieces for the site. Today I bring to you a small project involving NSTimers. Today we are going to build an app that represents a horse race. We will create a view with 6 small UIView squares with a blue background at the bottom of the screen, we will use a timer to move a random one of them forward a random amount of distance. Let’s get started!

Picture 16

Source

You can get the source here: NSTimerDemo

Steps

Step 1

Create a view based application in xCode. Call it whatever.

Step 2

In your view controller class header file add:

NSTimer *myTimer;

Step 3

In your view controller class, uncomment out the viewDidLoad method and fill in the following code:

- (void)viewDidLoad
{
      [super viewDidLoad];
 
      CGRect workingFrame;
      workingFrame.origin.x = 15;
      workingFrame.origin.y = 400;
      workingFrame.size.width = 40;
      workingFrame.size.height = 40;
 
      for(int i = 0; i &lt; 6; i++)
      {
           UIView *myView = [[UIView alloc] initWithFrame:workingFrame];
           [myView setTag:i];
           [myView setBackgroundColor:[UIColor blueColor]];
 
           workingFrame.origin.x = workingFrame.origin.x + workingFrame.size.width + 10;
           [self.view addSubview:myView];
      }
 
      myTimer = [NSTimer scheduledTimerWithTimeInterval:.1 target:self selector:@selector(moveACar) userInfo:nil repeats:YES];
  }

Step 4

In your view controller class, add the following method:

-(void)moveACar
{
      int r = rand() % 6;
      NSLog(@"My number is %d", r);
 
      for(UIView *aView in [self.view subviews])
      {
           if([aView tag] == r)
           {
                int movement = rand() % 100;
                CGRect workingFrame = aView.frame;
                workingFrame.origin.y = workingFrame.origin.y - movement;
 
                [UIView beginAnimations:nil context:NULL];
                [UIView setAnimationDuration:.2];
                [aView setFrame:workingFrame];
                [UIView commitAnimations];
 
                if(workingFrame.origin.y &lt; 0)
                {
                    [myTimer invalidate];
                }
           }
      }
}

Conclusion

So that is it. Timers are really cool and come in handy for all sorts of small problems in a project. Happy coding.

TypingWeb Offers Free Typing Lessons [Keyboards]

Whether you’re an able but slow touch typist, or you never graduated beyond hunting and pecking, TypingWeb is a free and easy to use online typing tutor that will help you hone your keyboard chops.

There is no registration necessary, you can dive right in and try out the basic lessons. Registering for an account lets you save your progress and other statistics. Along with a clean and easy to use interface, what sets TypingWeb apart from other free typing tutors is the ability to change the language and keyboard layout. Want to learn Dvorak without tearing your current keyboard apart? Learning to type on a keyboard other than an English QWERTY layout? Swap out the keyboard in the settings section.

TypingWeb is structured to take you from the home row all the way to efficiently using infrequent keys and key combination. There are also actually enjoyable typing games, unlike the lame typing games you may recall from grade school typing tutors. TypingWeb is free, but if you want to get rid of the ads, you can pay a one-time $9.99 fee to turn TypingWeb ad-free. Thanks Kratos!





Top 10 Tools for a Free Online Education [Lifehacker Top 10]

It’s easy to forget these days that the internet started out as a place for academics and researchers to trade data and knowledge. Recapture the web’s brain-expanding potential with these free resources for educating yourself online.

Photo by Sailor Coruscant.

10. Teach yourself programming

Coding, whether on the web or on the desktop, is one of those skills you’ll almost never regret having. Coincidentally, the web is full of people willing to teach, and show off, programming skills. Whether you’re looking to knock out a modest Firefox extension or tackle your first programming language, there's no requirement to run out and buy the thickest book you can find at Barnes & Noble. Google Code University, for instance, hosts a whole CSE program’s worth of straight-up coding lessons in its bowels. We’ve pointed out a lot of other programming resources found around the web, so you should be able to get started in almost any project. As for the random, unexpected, seemingly inscrutable bugs, well … welcome to the fold.

9. Get a Personal MBA

“MBA programs don’t have a monopoly on advanced business knowledge: you can teach yourself everything you need to know to succeed in life and at work.” The Personal MBA site occasionally updates its list of dozens of helpful business books, designed to teach both the nuts-and-bolts money stuff and the kind of thinking one needs to get ahead in sales, marketing, or wherever your interests lie. A business school can offer networking, mentoring, and other perks, but nobody can teach you enthusiasm and business savvy—except yourself.

8. Learn to actually use Ubuntu

Too often, newcomers to Ubuntu, the seriously popular Linux distribution, find that their questions about any problem great or small is answered with a curt “Search the forums,” or “Just Google it.” From experience, that’s like telling someone there’s maple sap somewhere in that forest, so here’s a nail and get moving. With a brand-new installation sitting on your computer, few resources are as straight-forward and comprehensive as the Ubuntu Guide, which is packed with common stuff like installing VLC and getting VLC playback, but spans across topics including Samba and remote printing configuration. Author Keir Thomas also offered Lifehacker readers a little preview of his Ubuntu Kung Fu in two excerpts that tweak one’s system into a faster, more efficient data flinger.

7. Get started on a new language

Nobody’s pretending you can talk like a local without some immersion experience. But there’s a lot of resources on the web for honing an already-sharpened second language, or at least picking up some of the vocab and nuances. Learn10 gives you 10 vocabulary builders delivered every day by email, through iGoogle, through an iPhone page, or most any other way you’d like. One Minute Languages podcasts its lessons and lets newcomers stream from the archives. And Mango Languages has about 100 lessons, shown to you in PowerPoint style with interstitial quizzes, to move you through any language without cracking a book. Not that books are bad, of course, but this is stuff you can crack out during a coffee break.

6. Trade your skills, find an instructor

As Ramit Sethi put it in our interview, many people don’t realize the value of the skills they do have, whether it’s something as simple as higher-level English or software lessons for those in need. A site like TeachMate capitalizes on the inherent disparities in our interests, letting someone willing to teach a bit of, for example, Russian language get cooking lessons in return. If a site like TeachMate doesn’t quite reach you, try Craigslist, which, especially in a recession, is brimming with people looking to trade skills instead of cash.

5. Academic Earth and YouTube EDU

We have to guess that having a giant, searchable database of free academic lectures was just too good an idea for two different web firms to pass up. Academic Earth has been described as a Hulu-like aggregator for lots of major universities’ content, and offers the slicker and more navigable front-end for them, as well as allowing embedding and sharing with no restrictions. YouTube EDU might have a broader reach, and the player and format might be a bit more familiar to most. Both sites offer both individual lectures and full course series, and are definitely worth checking out.

4. Teach yourself all kinds of photography

Sites like Photojojo and Digital Photography School are oft-linked resources around Lifehacker, and for good reason. They let the uber-technical shooters run wild in forums and discussion groups, but focus the majority of their front-page posts on things that beginning DSLR shooters and moderate consumer-cam photographers can grasp and mix into their daily camera work. Of course, we’ve compiled and sought out our own digital photography advice at Lifehacker, including photographer Scott Feldstein’s guide to mastering your DSLR camera (Part 1 and Part 2), and our compilation of David Pogue’s best photography tricks, plus ours. Then there’s the simple pleasures of posting on Flickr, seeking out Photo by Marcin Wichary.

3. Get an unofficial liberal arts major

Whole-mind learning doesn't end the day you declare a major and start sending out resumes. A huge number of universities offer up some of their most unique and fascinating resources for free online, posting up databases, image galleries, and all kinds of stuff you wish you had time to dig through during your undergrad years. Learn everything you ever wanted to about Picasso at Texas A & M's Picasso Project. Indulge your inner geo-geek with super hi-res images from Hirise at the University of Arizona. Tour the world’s spaces in 3D with The World Wide Panorama at UC Berkeley. Wendy Boswell discovered those resources and way more in her discovery of the .edu underground, and you can find a lot more down there, too.

2. Learn an instrument

If being dropped off at the music store/mall/piano teacher’s house wasn’t a memorable part of your childhood, you might dig the digital age’s equivalents a lot more. Guitar players, in particular, have a lot of places to turn for video, audio, and graphical teaching tools. Adam rounded a lot of them up in his guide to learning to play an instrument online. If you want to build a foundation for learning any instrument, though, Ricci Adams’ Musictheory.net has Flash-based tutorials that offer a gentle tour through keys, time signatures, modalities, and the other ins and outs of notes and chords.

1. Learn from actual college courses online

A huge number of colleges, universities, and other degree-granting universities are going all open-source these days—giving away the actual guts of their courses, while retaining their revenue stream by awarding degrees only to those who pay. In this day and age, though, programming, marketing, design, and other self-taught skills are pretty valuable, however you came by them. Whether you're looking to break into a field or just augment your skill set, dig into our guide to getting a free college education online, which we then updated a bit with Education Portal’s list of ten universities with the best free online courses. Just think about it—at home, with your coffee and comfortable chair, you're far more awake than the average co-ed who totally should have hit the hay a bit earlier last night.

Where do you turn when you have to teach yourself something? What skills or topics would you like to see more coverage of on Lifehacker, or just anywhere on the web? Help us plan a curriculum in the comments.





WP Like Button Plugin by Free WordPress Templates