How to migrate from UIActionSheet to UIAlertController in Objective-C

How to migrate from UIActionSheet to UIAlertController in Objective-C
Photo Table on iPad - Crashed when deleting photos using the UIActionSheet

I have an old app named Photo Table. It has been off the App Store because I didn't update it to use the latest APIs for UIAlertController. My app is still using UIActionSheet in at least 6 places.

How to reproduce the crash on iPad (not iPhone):

  1. Tap on the Album settings button
  2. Select "Manage Photos"
  3. Tap photos
  4. Tap "Delete"
Photo Table delete photos via UIActionSheet

CRASH!

What do I do?

The issue relates to how Apple has deprecated old APIs and how those APIs have changed over time.

My confirmation dialog is not working properly, and I'll show you how I migrated that code in Objective-C to use the new modern API for UIAlertController.

Old code in Objective-C

You can see the warning in your code, and it may crash on iPad.

// 'UIActionSheet' is deprecated: first deprecated in iOS 8.3 - UIActionSheet is deprecated. Use UIAlertController with a preferredStyle of UIAlertControllerStyleActionSheet instead

UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:@"Are you sure you want to delete these photos?" delegate:self cancelButtonTitle:@"No" destructiveButtonTitle:@"Delete Photos" otherButtonTitles:nil];
[actionSheet showInView:self.navigationController.view];
UIActionSheet delegate method:

There was a delegate method and that had all your logic to handle multiple action sheets. You'll need to grab logic from this method to use in your new UIAlertController code.

- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
    if(actionSheet.cancelButtonIndex == buttonIndex) { // Cancel
        ALog(@"Cancel: %ld", (long)buttonIndex);
        
    } else if(buttonIndex == 0) { // Delete Photos
        
        
        NSArray *filePaths = [album filePathsForImagesAtIndicies:selectedImageSourceIndicies];
        // ALog("file pathds = %@", filePaths);
        [album removeImagesAtIndexes:selectedImageSourceIndicies];
        
        [updateCTDelegate removeImagesWithFilePaths:filePaths];
        
        [self unselectAll];
        [self reload];

    } 
}

To transform this logic, I need to show either an ActionSheet in a popover (which is also deprecated) or an Alert.

When I presented it as an ActionSheet in my popover, it caused the memory to spike (I was probably doing something with the wrong popoverPresentationController because it spiked my memory to several gigabytes and hung the app.

Using UIAlertController caused a weird memory leak that hung the app... So I pivoted.

Update to UIAlertController in Objective-C

Instead of showing the Action Sheet, I opted for the Alert style to fix that memory leak.

UIAlertController *actionSheet = [UIAlertController alertControllerWithTitle:title
message:message                                                                  preferredStyle:UIAlertControllerStyleAlert];

I used the ternary operator so that I could customize the delete message based on if one photo was selected or multiple photos were selected.

- (void)deleteBarButtonPressed:(id)sender {    
    NSString *title = selectedImageSourceIndicies.count > 1 ? @"Delete Photos" : @"Delete Photo";
    NSString *message = selectedImageSourceIndicies.count > 1
    ? @"Are you sure you want to delete these photos?"
    : @"Are you sure you want to delete this photo?";
    
    UIAlertController *actionSheet = [UIAlertController alertControllerWithTitle:title
message:message                                                                  preferredStyle:UIAlertControllerStyleAlert];

    UIAlertAction *deleteAction = [UIAlertAction actionWithTitle:@"Delete" style:UIAlertActionStyleDestructive
           handler:^(UIAlertAction * action) {
        ALog(@"Delete selected photo(s)");
        [self deleteSelectedPhotos];
    }];
    [actionSheet addAction:deleteAction];
    
    UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel
           handler:^(UIAlertAction * action) {
    }];
    [actionSheet addAction:cancelAction];
    
    [self presentViewController:actionSheet animated:YES completion:^{
        ALog(@"DONE delete presentation!");
    }];
}

Delete logic

- (void)deleteSelectedPhotos {
    NSArray *filePaths = [album filePathsForImagesAtIndicies:selectedImageSourceIndicies];
    [album removeImagesAtIndexes:selectedImageSourceIndicies];
    [updateCTDelegate removeImagesWithFilePaths:filePaths];
    [self unselectAll];
    [self reload];
}

ALog instead of NSLog

I used ALog in my code to print function names and line numbers.

// ALog always displays output regardless of the DEBUG setting
#define ALog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);

I hope this helps you fix your issues if you're updating a really old app like mine.