break;

This is another programming tool that I don’t recall ever using before. Normally in a loop I cycle through the elements and do something with each item. For example, this Objective C method loops through all the words in the shuffledWords array and returns a list of the words. The for loop in this case uses ‘fast enumeration’ to select each object in the array.


- (Word *)getAndOrButWord:(NSString *)group {
    Word *wordToReturn;
    for( Word *aWord in self.shuffledWords ) {
        if ( [group isEqual:aWord.group] ) {
            wordToReturn = aWord;
        }
    }
    return wordToReturn;
    
}

And this is part of a method that uses the more traditional for loop that explicitly loops through all of the items in an array.


for (NSInteger i = 0; i < [self.prefsCategory1 count]; i++) {
            if ( [[self.prefsCategory1 objectAtIndex:i] isEqualToString: @"1"]) {
                NSString *levelAndPart = [NSString stringWithFormat:@"PREFS01_NAME Part %i", i];
                [self.selectedCategories addObject:levelAndPart];
            }
        }

In both of these cases each item is looked at and appropriate action taken. However, you can break out of the loop early if you don’t need to look at each item. In this simplified example, I only need four items that match the criterion so there is no point in looping after I’ve found four.


for ( Word *gWord in wordsInGroup ) {
    [wordListToReturn addObject:gWord];
    if ( [wordListToReturn count] == 4 ) break;
}
NSLog(@"I've broken out of the loop");

Control goes out of the loop entirely, just as if all the items had been looked at.
Here is a real world example with multiple break statements. In this case I have an array of 200 objects (girls with colored backpacks) and I want four items from the array. The backpacks are colored and have a different colored stripe on them. In the game I ask the child to show me the backpack that is, for example, red and green. But I don’t want to display a green and red backpack on the screen since it would be confusing to the child. There are two breaks in this example. First I loop through the ‘wordListToReturn’ array to see if the backpack can be added. If it can’t then there is no point in looking at the rest of the items in the array so I break out. This takes me to the outer loop and I pick the next object in the backpack array. Once I get four items I don’t need to continue, so there is another break that takes me out of the loop entirely.


NSInteger wordsToReturnCount = 1;
    for ( Word *gWord in wordsInGroup ) {
        // Add the first item to the list
        if ( ![wordListToReturn lastObject]) {
            [wordListToReturn addObject:[wordsInGroup objectAtIndex:0]];
            NSLog(@"First Word added %@", [wordListToReturn objectAtIndex:0]);
        // Loop through after the first word in in the list
        } else {
            BOOL addWord = YES; // Assume you'll add the word unless there is a match
            NSLog(@"gword is %@", gWord.image);
            // Look through all the words in the return list and see if this word matches
            for ( Word *lWord in wordListToReturn) {
                NSArray *gWordColors =[NSArray arrayWithObjects:gWord.color1,   gWord.color2, nil];
                NSArray *lWordColors = [NSArray arrayWithObjects:lWord.color1, lWord.color2, nil];
                
                [gWordColors sortedArrayUsingSelector: @selector(caseInsensitiveCompare:)];
                [lWordColors sortedArrayUsingSelector: @selector(caseInsensitiveCompare:)];
                
                NSString *sortedgWordColors = [NSString stringWithFormat:@"%@ %@", [gWordColors objectAtIndex:0], [gWordColors objectAtIndex:1] ];
                NSString *sortedlWordColors = [NSString stringWithFormat:@"%@ %@", [lWordColors objectAtIndex:0], [lWordColors objectAtIndex:1] ];
                NSLog(@" gWord: %@, lWord %@", sortedgWordColors, sortedlWordColors);
                if ([sortedgWordColors isEqualToString:sortedlWordColors]) {
                    addWord = NO;
                    break;
                }
            }
            if (addWord) {
                [wordListToReturn addObject:gWord];
                NSLog(@"Word added");
                wordsToReturnCount++;
            }
            if (wordsToReturnCount == 4 ) break;
        }
    }

Updating apps in iOS – Icons for Retina Display

The Apple documents on icon sizes is a bit out of date. It does not include the icon for the new retina iPad. And it is not updated for the new 1024×1024 iTunesArtwork requirement.

You need to include a new file that is 144×144 pixels and call it ‘Icon-72@2x.png’.

Then add it to your icons list in the Info.plist file.

Since I have lots of apps, I edited the Info.plist files in BBEdit and cleaned out all of the old icon files. You can also edit them in XCode by right-clicking on the Info.plist and choosing ‘Open As-Source Code’. The original files had the icon information between the ${EXECUTABLE_NAME} and CFBundleIdentifier keys so the new file looks like this.


  <string>${EXECUTABLE_NAME}</string>
  <key>CFBundleIconFiles</key>
  <array>
  <string>Icon.png</string>
  <string>Icon@2x.png</string>
  <string>Icon-72.png</string>
  <string>Icon-72@2x.png</string>
  <string>Icon-Small-50.png</string>
  <string>Icon-Small.png</string>
  <string>Icon-Small@2x.png</string>
  </array>
  <key>CFBundleIdentifier</key>

XCode will use the files to populate the icon display in the summary view, so you can check to see if you did everything correctly. If no icon shows up, make sure they are associated with the app and then drag the icon to the appropriate empty place in the summary. You should get an error message telling you why the icon is not appropriate. Usually it’s a size issue. If it fills in the spot, you probably have a naming issue.

Icon is 57×57 and Icon-Small is 29×29. The rest are obvious from the naming convention. The rest of the sizes are listed in the document, referenced below, along with their intended use.

According to the Apple document Core Foundation Keys you shouldn’t append the .png extension so that the system will automatically use the @2x version when appropriate. However, this doesn’t work for me.

XCode 4.5 doesn’t support any version of iOS before 4.3 so do not use CFBundleIconFile.

You also need to include a file called iTunesArtwork and iTunesArtwork@2x (no .png extension) in your application bundle that are 512×512 and 1024×1024 pixels respectively. Do not list them in the Info.plist file.

Two posts with more info Jared Sinclair and Peter Levine

Updating apps in iOS – Retina Display

I have a bunch of small icons that I use in my apps and for all of them, I just doubled the size of the image and added @2x to the name. For most of the icons that was all I needed to do because the frame I created for the images was a fixed number of points. iOS scaled the images appropriately. For some images I determined the frame size by looking at the size of the image. For those images I had to divide by the scale factor or the images would be twice as big as I wanted. e.g.


CGFloat deviceScale = [UIScreen mainScreen].scale;
cButton.frame = CGRectMake(0, 0, cImage.size.width/deviceScale, cImage.size.height/deviceScale);

All of my games rely heavily on graphics and they are large—too large to include both a normal size and @2x version. I can get the device to display the images as if they were labeled @2x by a simple conversion.


if ([[Utilities deviceType] isEqual:@"iPhone Retina4"] || [[Utilities deviceType] isEqual:@"iPhone Retina35"] ) {
        pictLeft  = [UIImage imageWithCGImage:pictLeft.CGImage  scale:2 orientation:pictLeft.imageOrientation];
        pictRight = [UIImage imageWithCGImage:pictRight.CGImage scale:2 orientation:pictRight.imageOrientation];
    }

This works on the iPhone because the images are way bigger than they need to be. On the iPad they aren’t more than twice the number of pixels that are displayed, so it doesn’t work.

Updating apps to iOS6 – Background images

The new iPhone is taller than the current phones. The default background images need to have a height (in pixels) of 1136 instead of 960 in the original. background images in the game need to be 176 pixels taller. The actual size depends on what the app does for top and bottom toolbars. Landscape backgrounds need to be 1136 pixels wide.

Since I have lots of games, I wrote a simple shell script to make copies the @2x images for the portrait and landscape and the default. You are required to name the default image ‘Default-568h@2x.png’ and iOS will automatically use it when launching on the new iPhone. In fact, that’s how the phone can tell that an app has been updated for the new dimension—otherwise it runs letterboxes. I kept the naming convention for the rest just to make things consistent.


#!/bin/bash
cd /Users/jscarry/Documents/Words/Words/BG\ Default-ArticIV/
cp BG@2x.png BG-568h@2x.png
cp BGLandscape@2x.png BGLandscape-568h@2x.png
cp Default@2x.png Default-568h@2x.png
exit

iOS will not automatically look for background images that fit the new phone’s dimensions. I needed to write a conditional statement to load the correct image if a new iPhone was detected.


UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation]; // iPad doesn't work with device orientation
    if(orientation == UIInterfaceOrientationPortrait || orientation == UIInterfaceOrientationPortraitUpsideDown) {
        if ([[Utilities deviceType] isEqualToString:@"iPhone Retina4"]) {
            self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"BG-568h"]];
        } else {
            self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"BG"]];
        }
    } else {
        if ([[Utilities deviceType] isEqualToString:@"iPhone Retina4"]) {
            self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"BGLandscape-568h"]];
        } else {
            self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"BGLandscape"]];
        }
    }

Note that it will automatically detect the retina display, so you don’t put @2x in the name. I redraw the screen when the device rotates, so I copied this code to the -(void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)orientation duration:(NSTimeInterval)duration method.

I added a method to my Utilities.m to detect device type. That’s why you see this line:


[Utilities deviceType] isEqualToString:@"iPhone Retina4"]

But all you need to test for is the height:


if (screenSize.height == 568) {
            device = @"iPhone Retina4";
}

do } { while

I’ve been coding in coding in various languages since around 1983 and this is the first time I’ve had an occasion to use a do } { while loop.

Here’s the scenario. I have four different kinds of pictures: locomotives, boxcars, tankers, and cabooses. I have eleven versions of each, one for each of eleven colors. I want to display two items on the screen at the same time and ask the child to identify the color. Now if a red boxcar and a red caboose show up on the screen at the same time, both are correct so the child can’t choose the red one. So what I want to do is check to see if the colors are the same and then pick a different object if they are.

I’ve already picked my first object from the list and this is the code I use to pick the second. I always need to pick a second item, so the do { } while construction is perfect. It runs through the code and after the first pass evaluates the conditional. In this case, it checks to see if the color of the first object (leftItem) is the same as the color of the second object (rightItem). If they are equal, it does another iteration and picks another object. I have eleven colors so the loop repeats about 9% of the time, so it doesn’t have any impact on execution. If you only had two or three colors, you’d probably want to use a different method.

Note: The code is Objective C and I changed it a bit from the original to make the loop portion clearer.


  NSInteger randomWord2;
        do {
            // Get another random number between 0 and n-1 
            //and add it to the original number plus 1
            int randomNumber2 = (arc4random() % numItems);
            // If randomNumber2 is not zero then the two words will be different
            if (randomNumber2   == 0) randomNumber2 = self.scoreKeeper.currentScreen + 1;
            randomItem2 = (randomNumber2 + self.scoreKeeper.currentScreen) % numItems;
            self.rightItem = [self.wordList getWord:randomItem2];
        // If the two objects have the same color, look for another rightItem
        } while ( [self.rightItem.color isEqual:self.leftItem.color] );