Update UIAlertView to UIAlertController

UIAlertView and UIActionSheet have been deprecated in iOS8. They still work and I submitted all of my apps without changing anything, but you’ll need to do it sometime. Now that my apps are all updated for the new phones, I have some time to do these type of nice to have’s. At first glance it appears difficult to update, but it really isn’t that hard.

The first place I updated was in the AppDelegate. I have an alert that asks the user to either start a new session (with new scoring and word choice) or resume the current session (keeping all of the current settings). The old code runs in the applicationWillEnterForeground method.

 
- (void)applicationWillEnterForeground:(UIApplication *)application {
    
    if (self.window.rootViewController) {
        NSString *messageWithTitle = [NSString stringWithFormat:@"Do you want to resume playing %@ or start a new session?", GAME_NAME_TITLE];
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Welcome Back"
                                                        message:messageWithTitle
                                                       delegate:self
                                              cancelButtonTitle:@"Resume"
                                              otherButtonTitles: @"Start New Session",nil];
        [alert show];
    }
}

Because I want to continue to run the app in versions prior to iOS8, I need to change this to a conditional. The code makes use of a #define from my Project-Prefix.pch


#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v)  ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)

Then I created two methods, one just has the code from the original alert and the other is new.
 
- (void)applicationWillEnterForeground:(UIApplication *)application {
    
    if (self.window.rootViewController) {

        if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"8.0")) {
            [self displayUIAlertController];
        } else {
            [self displayUIAlertView];
        }
    }

This is the new code. The title is the first line of the alert. The message is the second and additional lines. What’s different about this from the old way is that you add actions using methods with completion blocks. You can do anything in a block that you normally would. I kept mine simple and just called a method. If was writing this just for iOS8, I’d probably put the contents of the method in the block. Note: I could have used UIAlertActionStyleCancel instead of UIAlertActionStyleDefault for the Resume button. If you use the UIAlertActionStyleCancel style, it always appears at the end and is bolded
 
- (void)displayUIAlertController {

    NSString *alertMessage = [NSString stringWithFormat:@"Do you want to resume playing %@ or start a new session?", GAME_NAME_TITLE];
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Welcome Back"
                                                                   message:alertMessage
                                                            preferredStyle:UIAlertControllerStyleAlert];

    // You can add as many actions as you want
    UIAlertAction *startNewSession = [UIAlertAction actionWithTitle:@"Start New Session" 
                                                              style:UIAlertActionStyleDefault
                                                            handler:^(UIAlertAction *action) {
       [self startNewSession];
    }];

    UIAlertAction *doNothingAction = [UIAlertAction actionWithTitle:@"Resume"
                                                              style:UIAlertActionStyleDefault
                                                            handler:^(UIAlertAction *action) {
                    // Do nothing
    }];

    // Add actions to the controller. The order here determines the order in the alert. The last one is bolded.
    [alert addAction:doNothingAction];
    [alert addAction:startNewSession];

    // Finally present the action
    [self.window.rootViewController presentViewController:alert animated:true completion:nil];
}

Normally you’d display the alert with

[self presentViewController:alert animated:YES completion:nil];

but since this is the app delegate you need to do it slightly different.

This is the old code, just put into a method.

 
- (void)displayUIAlertView {
    NSString *messageWithTitle = [NSString stringWithFormat:@"Do you want to resume playing %@ or start a new session?", GAME_NAME_TITLE];
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Welcome Back"
                                                    message:messageWithTitle
                                                   delegate:self
                                          cancelButtonTitle:@"Resume"
                                          otherButtonTitles: @"Start New Session",nil];
    [alert show];
}

I pulled the code out of the buttonIndex1 section and created a new method that is called by both versions when a new session is started.

 
#pragma mark - Alert on restart
// buttonIndex 0 is cancel and the game continues
// buttonIndex 1 is Start New Session and the old results are saved and new session started
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
    if (buttonIndex == 1) {
        [self startNewSession];
    }
}

- (void)startNewSession {

    // Delete current results
    [Utilities copyCachedResultsToFile];
    [Utilities removeFileFromCache:@"Results.txt"];
    // When deciding whether to start a new table, compares the current scoringType to previousScoringType
    [Globals sharedInstance].previousScoringType = @"";
    [self.navigationController popToRootViewControllerAnimated:YES];
}

And this is what it looks like.

UIAlertControler example

Initializing Booleans

I have a globals method that I use to keep track of global values in my apps. It is mostly for options. I override the getter like this. In this example, if trials per round is not set, I initialize it to the #defined value for that app.


- (NSUInteger)trialsPerRound {
    
    if ( !_trialsPerRound ) _trialsPerRound = TRIALS_PER_ROUND;
    return _trialsPerRound;
}

I tried to do the same thing for some Booleans, but ran into a problem.


- (BOOL)reviewIgnored {
    
    if ( !_reviewIgnored ) _reviewIgnored = YES;
    return _reviewIgnored;
}

I wanted the reviewIgnored value to start at YES if it was not set. But what happened is that when I would change it to NO in the options page it would be fine. But when I called it in my if statement


  if (![Globals sharedInstance].reviewIgnored) {

what happened is that the getter checks its value, sees that it is NO. The if statement says to change reviewIgnored to YES and I get the wrong behaviour.

What I did was put all of my Boolean initialization in th AppDelegate.m file. Problem solved.

Validating apps so they will run on original iPads

Most of my apps were built with iOS4 or iOS5. They don’t require any of the new features of iOS6,7,8 so they will run fine on older devices that can’t upgrade. (I do have conditional code for iOS7s look and feel, and of course they run on the new iPhone sizes.) The original iPads will run iOS 5.1.1 but not any version that is newer. Unfortunately, the dropdown for choosing the minimum deployment target only lists iOS 5.1, which won’t validate.

Deployment dropdown

So how do you change this,
Deployment 5.1
to this?
Deployment 5.1.1

In my case I happen to be lucky in that Xcode 5 asked me if I wanted to fix the error and fixed it for me. So my project default is 5.1.1 and my problem is just to get it into the build settings. After much experimentation, I discovered that bringing up the deployment dropdown in the build phases column, closing it, then hitting the delete key fills in the box with the project default. I don’t know how to get the project default to contain 5.1.1 if it doesn’t already.

Icon Sizes for New iPhones and iOS8

To view the sizes, select the blank image space that you are interested in and click the size inspector. The size shows up at the bottom RHS along with the iOS version that it is targeted to.

Your screen should look something like this.

App icon sizes

iPhone 6 requires two new images.

180×180
Icon-iPhone-60@3x.png

87×87
Icon-Small@3x.png

If you look at the JSON for the appiconset it should look something like this. The first line has the sizes. Note that some icons are used in multiple places. The names are arbitrary but I kept the same naming convention as before.


{
  "images" : [
    {
      "size" : "29x29",
      "idiom" : "iphone",
      "filename" : "Icon-Small.png",
      "scale" : "1x"
    },
    {
      "size" : "29x29",
      "idiom" : "iphone",
      "filename" : "Icon-Small@2x.png",
      "scale" : "2x"
    },
    {
      "size" : "29x29",
      "idiom" : "iphone",
      "filename" : "Icon-Small@3x.png",
      "scale" : "3x"
    },
    {
      "size" : "40x40",
      "idiom" : "iphone",
      "filename" : "Icon-40@2x.png",
      "scale" : "2x"
    },
    {
      "size" : "40x40",
      "idiom" : "iphone",
      "filename" : "Icon-iPhone-60@2x.png",
      "scale" : "3x"
    },
    {
      "size" : "57x57",
      "idiom" : "iphone",
      "filename" : "Icon.png",
      "scale" : "1x"
    },
    {
      "size" : "57x57",
      "idiom" : "iphone",
      "filename" : "Icon@2x.png",
      "scale" : "2x"
    },
    {
      "size" : "60x60",
      "idiom" : "iphone",
      "filename" : "Icon-iPhone-60@2x.png",
      "scale" : "2x"
    },
    {
      "size" : "60x60",
      "idiom" : "iphone",
      "filename" : "Icon-iPhone-60@3x.png",
      "scale" : "3x"
    },
    {
      "size" : "29x29",
      "idiom" : "ipad",
      "filename" : "Icon-Small.png",
      "scale" : "1x"
    },
    {
      "size" : "29x29",
      "idiom" : "ipad",
      "filename" : "Icon-Small@2x.png",
      "scale" : "2x"
    },
    {
      "size" : "40x40",
      "idiom" : "ipad",
      "filename" : "Icon-40.png",
      "scale" : "1x"
    },
    {
      "size" : "40x40",
      "idiom" : "ipad",
      "filename" : "Icon-40@2x.png",
      "scale" : "2x"
    },
    {
      "size" : "50x50",
      "idiom" : "ipad",
      "filename" : "Icon-Small-50.png",
      "scale" : "1x"
    },
    {
      "size" : "50x50",
      "idiom" : "ipad",
      "filename" : "Icon-Small-50@2x.png",
      "scale" : "2x"
    },
    {
      "size" : "72x72",
      "idiom" : "ipad",
      "filename" : "Icon-72.png",
      "scale" : "1x"
    },
    {
      "size" : "72x72",
      "idiom" : "ipad",
      "filename" : "Icon-72@2x.png",
      "scale" : "2x"
    },
    {
      "size" : "76x76",
      "idiom" : "ipad",
      "filename" : "Icon-iPad-76.png",
      "scale" : "1x"
    },
    {
      "size" : "76x76",
      "idiom" : "ipad",
      "filename" : "Icon-iPad-76@2x.png",
      "scale" : "2x"
    }
  ],
  "info" : {
    "version" : 1,
    "author" : "xcode"
  },
  "properties" : {
    "pre-rendered" : true
  }
}

Background images for iOS

I have 27 apps in the store and did not want to create and manage new backgrounds for each of them. And I suspect that there will be more sizes in the future. So what I did was create an 1800×1800 image for each app. They are mostly around 300-400KB. Then in viewWillAppear I call this method.


- (void)pickBackgroundImage {

    UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
    CGFloat scale = [UIScreen mainScreen].scale;
    CGPoint midPoint = [Utilities findMidpoint:self.view];

    NSString *pictFile = [[NSBundle mainBundle] pathForResource:@"Background" ofType:@"png"];
    UIImage *imageToDisplay = [UIImage imageWithContentsOfFile:pictFile];
    imageToDisplay  = [UIImage imageWithCGImage:imageToDisplay.CGImage scale:scale orientation:imageToDisplay.imageOrientation];

    CGRect pictFrame;
    if(orientation == UIInterfaceOrientationPortrait || orientation == UIInterfaceOrientationPortraitUpsideDown) {
        CGFloat imageWidth = (unsigned int)(.9f * self.view.frame.size.width);
        pictFrame = CGRectMake(midPoint.x - imageWidth/2, midPoint.y - imageWidth/2, imageWidth, imageWidth);
        pictFrame.origin.y = self.view.frame.origin.y + .3f * pictFrame.origin.y;
    } else {
        CGFloat imageWidth = (unsigned int)(self.view.frame.size.height - 20 - 44);
        pictFrame = CGRectMake(midPoint.x - imageWidth/2, midPoint.y - imageWidth/2, imageWidth, imageWidth);
        pictFrame.origin.y = 10;
    }
    self.BGView = [[UIImageView alloc] initWithImage:imageToDisplay];
    self.BGView.frame = pictFrame;
    self.BGView.contentMode = UIViewContentModeScaleAspectFit;

    [self.view insertSubview:self.BGView atIndex:0];
}

The arithmetic is a little tricky, but basically I figure out how big the screen is, then subtract the menubar and bottom bar. Then fill 90% of whats left with the image. I use the scale factor, CGFloat scale = [UIScreen mainScreen].scale;, to make the device think that the image was created just for it, e.g. @2x or @3x.

Works in the simulator for all devices. I don’t have a new phone so I haven’t been able to test it on one yet.