What do people eat?

January 13th, 2012

Here's an interesting way of looking at what people eat: Looking at what people do calorie queries for on Google.

201201130836

The winners are:

apple
banana
almonds
avocado
beer
banana
broccoli
blueberries
chicken breast
chicken
coffee
carrots
dates
domino's pizza
dried apricot
dried cranberries
egg
egg whites
edamame
eggplant
fruit
fish
flour
grapes
grapefruit
gin
green beans
honey
hummus
hard boiled egg
ham
ice cream
indian food
iceberg lettuce
italian sausage
jello
jelly beans
jack daniels
jujubes
kiwi
kale
kfc
kraft dinner
lettuce
lasagna
lean ground beef
lentils
milk
mcdonalds
mushrooms
mashed potatoes
nuts
naan
nanaimo bar
nachos
orange
oatmeal
one egg
onion
pizza
popcorn
pumpkin seeds
pomegranate
quinoa
quiznos
quiche
red wine
rice
raspberries
rum
sushi
sweet potato
salmon
sugar
tim hortons
turkey
tomato
tea
udon
unsweetened applesauce
vodka
vegetables
wine 
white wine
watermelon
walnuts
xylitol
xanthan gum
xenergy
yogurt
yam
yellow pepper
zucchini

There you have it; a list of foods that's not really much use for anything. But still interesting.

Apple Analysts

January 12th, 2012

It's amazing the discongruity between the "professional" analysts of Apple's financials, and the "amateurs" like Horace Dediu of Asymco.

I put "professional" and "amateur" in quotes because it seems like in this case, the results you get from these two groups are opposite from what you'd expect. The financial market, and professional analysts, have been underestimating Apple for years now.

This story at Market Watch, "Time to Sell Apple", is a perfect example. Here's a few points from this article.

If a business fails to take care of its customers, its customers will go elsewhere … In the case of Apple, this is happening in front of our eyes, and the risks are therefore very high. Apple has stopped serving their customers well, and unless they start to serve their customers better the company will begin to lose more market share and revenue and earnings projections will come down aggressively.

But this whole statement is based on this assertion:

Most people think Apple's customers are the end user, but … the real customers are third-party resellers, and Apple is not treating them right.

This is just flat out wrong. Apple has never pandered to the carriers and resellers. They're the only phone manufacturer that doesn't, and it's one of the reason their real customers, the folks using the phones, prefer their products.

iPhone users are people who want an iPhone. Android users are, mostly, people who want a phone, and went with the one the carrier suggested.

The company's high-growth phase is behind it

This is where the analysts methods fail. They can only analyze the data they have available, and they have no data about future products. But it's these future products, the ones we don't know about yet, that is where Apple's growth comes from.

I don't think the market has any model that can account for this, so they look at each new, incredibly successful, incredibly profitable thing that Apple does as if it's the last one. Because how could they do otherwise?

An Apple TV

January 3rd, 2012

I don't really understand why Apple would get into the TV business.

Apple creates new markets. Moving into an established market like the TV business, with all the content complexity and interconnection requirements, just doesn't seem like something they'd do. It's a highly competitive space with low margins; not Apple's kind of business.

And the idea that the world needs an Apple TV because TVs are too hard to connect seems off the mark. Hooking up a TV isn't difficult; it's hooking up all the other devices - the cable box, the PS3, the Xbox 360 with the Kinect, the Wii and its sensor bar, that make it difficult. Unless Apple is planning to take over the jobs that all the other devices people do with things hooked up to their TVs, they're not going to be making things that much simpler.

An "Apple TV" without some pretty amazing content deals would still require a cable box, and if they could pull off those deals, why tie it to a new TV? Why not just making it available through the Apple TV for everyone?

My iTunes Match Experience

December 16th, 2011

I'm in the middle of setting up with iTunes Match. So far, it's been anything but smooth.

First off, I signed up during the brief window when iTunes Match was available for sale but not actually working in Canada, so I had a completely broken service for a day or so.

But once it was officially switched on, it started working. There are 3 steps to iTunes Match:

201112161716

Step 1 was painless. It whipped through my ~23k songs in an hour or so, and then went on to step 2. I left it at step 2 overnight, and when I woke up in the morning, it was stuck. It was about 80% done, but not moving.

I stopped iTunes Match and restarted it, and this time paid attention to where it stopped. This time it made it to song #22548, and then hung. Stopped, restarted, and it made it to 22549. Progress, but the 10 minutes or so it took for every attempt to make it to that song number was painful. I was watching with fs_usage to see if it was getting hung up on a certain album or something, but every time it stopped it was on something completely different.

But after 2 more restarts, it made it all the way through my music library. Now it's on Step 3, uploading, and and with 4103 items to go, this one's going to take a while.

The message I wanted to convey in this post is: If iTunes Match is hanging during Step 2, just stop it and restart it. Eventually it will complete.

Update three days later: It's just over half done. About 1950 songs to go. I'm on DSL, so my upstream bandwidth is about 1 megabit.

iTunes Match Canada Rollout

December 15th, 2011

Or not-rollout as the case may be. Last night I read that iTunes Match had been switched on in Canada, so I fired up iTunes and went to my account page, and there was an option to Learn More about iTunes Match. I clicked on that and it let me sign up.

But after that .. nothing. No new UI to let me sync my songs to the cloud.

Either this is was a mistaken rollout and they've switched it off (in which case they've got a lot of refunding to do) or they are having problems with all the extra subscribers and the problem will sort itself out shortly.

I'm thinking it was a mistaken rollout. Why? Because check this out:

It would be so out of character for Apple to ship UI with a typo. I'm guessing this was supposed to be some sort of test release that accidentally got pushed live.

Update: It's really live now, and working for me. Typo and all.

UITableView selection not sticking when reloading cells

December 12th, 2011

I have some UITableView code that was setting a selection, and then later making some other changes to the cell using reloadRowsAtIndexPaths:withRowAnimation: and I was having trouble figuring out why my selection wasn't sticking. I'd select an object, but it wouldn't show as selected.

Turns out reloadRowsAtIndexPaths:withRowAnimation: will deselect whatever rows you reload. Check this out:

[cc lang="objc"]
// Create an NSIndexPath that indicates the 3rd row
NSIndexPath *path = [NSIndexPath indexPathForRow:3 inSection:0];

// Select it
[self.tableView selectRowAtIndexPath:path
animated:NO
scrollPosition:UITableViewScrollPositionNone];

// Is it still selected?
path = self.tableView.indexPathForSelectedRow;

// Yes, this will print the values you'd expect for that row being selected
NSLog(@"Path: %@", path);

// Reload that row
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:path] withRowAnimation:UITableViewRowAnimationNone];

// Now is it still selected?
path = self.tableView.indexPathForSelectedRow;

// Negatory, path is null.
NSLog(@"Path: %@", path);
[/cc]

It's not obvious from the documentation that this operation affects the selection, and it took me a while to figure out why my selection was being dropped. The fix is simple enough: Preserve the selection yourself around the reload call.

[cc lang="objc"]
// Save the current selection
NSIndexPath *savedSelection = self.tableView.indexPathForSelectedRow;

// Reload
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:path] withRowAnimation:UITableViewRowAnimationNone];

// And restore the selection
[self.tableView selectRowAtIndexPath:savedSelection
animated:NO
scrollPosition:UITableViewScrollPositionNone];
[/cc]

Reinstalling Xcode

December 12th, 2011

I ran into a problem with my Xcode install. I had actually just dragged it over from another volume, and this seemed to work, but I was having some trouble running Instruments, so I decided I'd just reinstall it. I uninstalled Xcode, went to redownload it, but the Mac App Store still said it was installed.

Hmm. Now what? There is no direct download link for Xcode; the only way to get it is through the store.

When you get Xcode through the Mac App Store, what you actually get is an app called "Install Xcode".

You run this app and it installs Xcode. It leaves itself in your /Applications folder, and it's this app that has the Mac App Store receipt file identifying it as an app that's installed, so normally all you need to do is get rid of this "Install Xcode" application for the store to stop seeing Xcode as installed.

But this didn't work for me. I removed the installer, went back to the store, and it still wouldn't let me download.

To figure out how the App Store was finding Xcode installed, I used the fs_usage tool:

sudo fs_usage >~/out.txt

Run this in Terminal before starting the App Store. This command will log all file system activity to a file named 'out.txt' in your home folder. With it running, launch the App Store. Then, back in the console, hit Ctrl-C to stop fs_usage, and open the out.txt file in an editor. Search for the string "Xcode", and you should see some lines like this:

10:04:36 lstat64 /Volumes/Steve's Laptop Backup/Applications/Install Xcode.app 0.000033 App Store
10:04:36 getattrlist /Volumes/Steve's Laptop Backup/Applications/Install Xcode.app 0.000006 App Store
10:04:36 getattrlist /Volumes/Steve's Laptop Backup/Applications/Install Xcode.app 0.000009 App Store

Aha! I had a drive mounted that had an old Applications folder, where the installer was already downloaded. I have no idea how I would have found this otherwise.

(I had tried searching for the Xcode installer using Spotlight but I had told Spotlight not to index that backup drive, so it didn't find it).

Creating a UIView with a Tiled Background

December 6th, 2011

Tiling a background texture is a pretty common thing to want to do. Instead of creating one large texture that fills the entire background, instead the goal is to take a smaller image, say 256x256, and repeat it horizontally and vertically to fill the background.

As far as I can tell, there's no way to do this in Interface Builder. You can create a UIImageView to use as the background of a view, but the options for placing the image don't include tiling.

Turns out tiling is just a special kind of colour.

[cc lang="objc"]
UIImage *image = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"corkboard" ofType:@"png"]];
self.backgroundColor = [UIColor colorWithPatternImage:image];
[/cc]

This loads an image called "corkboard.png" and sets it as a repeating pattern by using it as the view's backgroundColor. The colour itself is a special kind of "colour" that's actually the image itself, repeating over whatever space the color needs to fill.

Not a bad way to do it, but the odd location makes it a bit hard to discover.

UIManagedDocument autosave troubleshooting

December 3rd, 2011

I'd been having some trouble with UIManagedDocument that turned out to be my fault, but it wasn't easy to figure out. In the end there were a couple of things that helped me figure out what was going on, so I'm posting them here in the hopes that I'll save someone else some time.

UIManagedDocument is a UIDocument subclass which stores document data in a Core Data database. UIDocument is a class that manages document storage for you, but provides some great features like iCloud integration and automatic saving.

It's the automatic saving that I was having trouble with. I would create my UIManagedDocument subclass, create a new document, populate it, and all seemed well. But then later, I'd create a new instance of a managed object, add it to my document, expect it to automatically save.. but it wasn't saving.

Core Data is fairly well documented, and the process of creating a UIManagedDocument is also well documented, so I'm not going to post a whole walkthrough here. To make autosave work, the key thing you need to do is either add something to the document's undo manager, or use updateChangeCount to inform the document that you've made a change. I was doing the latter.

The first thing I found useful was to subclass UIManagedDocument, and add an override:

[cc lang="objc"]
- (id)contentsForType:(NSString *)typeName error:(NSError *__autoreleasing *)outError
{
NSLog(@"Auto-Saving Document");
return [super contentsForType:typeName error:outError];
}
[/cc]

You don't want to leave NSLog messages in a shipping release binary, so do something about that, but the goal here is to know when the save is happening. One reason for this is if you make a change to the document, and then kill the app before the change is autosaved, it doesn't get saved. Seeing the save in the console is your sign that you can kill the debug session knowing your change has been committed.

The other thing you need to know is when errors happen. When UIManagedDocument runs into a Core Data error, your only opportunity to find out is through an override:

[cc lang="objc"]
- (void)handleError:(NSError *)error userInteractionPermitted:(BOOL)userInteractionPermitted
{
NSLog(@"UIManagedDocument error: %@", error.localizedDescription);
NSArray* errors = [[error userInfo] objectForKey:NSDetailedErrorsKey];
if(errors != nil && errors.count > 0) {
for (NSError *error in errors) {
NSLog(@" Error: %@", error.userInfo);
}
} else {
NSLog(@" %@", error.userInfo);
}
}
[/cc]

Without this override, your auto-save may fail, and you'll never know about it. In my case, I was getting a 1570 error, which, when looked up here, turns out to be a missing mandatory property: I forgot to set a mandatory property on one of my objects, so the save was failing and I had no idea until adding this override.

Building a Dynamic UIActionSheet

December 3rd, 2011

Most of the examples I see for UIActionSheet (the iOS pop-up menu) show static choices and checking for which one was picked by using constant values. That's not going to work if your menu needs to be dynamic.

For example, if you want to have an option to Email or Print an item, you might want this on a UIActionSheet menu. But if either of these things aren't available (for example, you may be running on a device that doesn't support printing, or there may not be an email account configured) then you shouldn't show these items.

Here's one way to do this.

Start out by creating localized string variables for the menu items.

[cc lang="objc"]
NSString *MPActionMenuCancelItem = NSLocalizedString(@"Cancel", @"Cancel item in Action Sheet");
NSString *MPActionMenuDeleteItem = NSLocalizedString(@"Delete", @"Delete item in Action Sheet");
NSString *MPActionMenuEmailItem = NSLocalizedString(@"Email", @"Email item in Action Sheet");
NSString *MPActionMenuPrintItem = NSLocalizedString(@"Print", @"Print item in Action Sheet");
[/cc]

Note that I'm using NSLocalizedString here and not simply using a string value.

Now you need to dynamically build the UIActionSheet. Here's code to do that:

[cc lang="objc"]
- (IBAction)actionSelected:(id)sender
{
UIActionSheet *actions = [[UIActionSheet alloc] initWithTitle:nil
delegate:self
cancelButtonTitle:nil
destructiveButtonTitle:nil
otherButtonTitles:nil];

if ([MFMailComposeViewController canSendMail])
[actions addButtonWithTitle:MPActionMenuEmailItem];

if ([UIPrintInteractionController canPrintURL:[NSURL fileURLWithPath:imageData.path]])
[actions addButtonWithTitle:MPActionMenuPrintItem];

actions.destructiveButtonIndex = [actions addButtonWithTitle:MPActionMenuDeleteItem];
actions.cancelButtonIndex = [actions addButtonWithTitle:MPActionMenuCancelItem];

[actions showFromBarButtonItem:sender animated:YES];
}
[/cc]

Notice how email and printing are only included if supported.

It's convenient that addButtonWithTitle: returns the button index that was added, so we can assign the button indexes for those directly.

When the user picks an item from the UIActionSheet, we need to figure out what they picked and act on it.

[cc lang="objc"]
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex == actionSheet.cancelButtonIndex)
return;

if (buttonIndex == actionSheet.destructiveButtonIndex)
[self deleteItem];

NSString *clicked = [actionSheet buttonTitleAtIndex:buttonIndex];
if ([clicked compare:MPActionMenuEmailtem] == NSOrderedSame)
[self emailItem];

if ([clicked compare:MPActionMenuPrintItem] == NSOrderedSame)
[self printItem];
}
[/cc]

I thought about building a table of indices to action values or something more complex like that, because comparing the selected string somehow just doesn't feel right. That may be my years as a Windows developer showing through, because this way is straightforward, and works great.