From 463746244e68d04fc43c9c058acc5d420633b47e Mon Sep 17 00:00:00 2001 From: Nikita Lutsenko Date: Thu, 7 Jan 2016 14:21:21 -0800 Subject: [PATCH] Use UIAlertController to present alerts if it's available. --- .../Internal/Extensions/PFUIAlertView.h | 46 ++++- .../Internal/Extensions/PFUIAlertView.m | 191 ++++++++++++++++-- .../PFLogInViewController.m | 48 ++--- .../PFProductTableViewController.m | 8 +- .../PFQueryCollectionViewController.m | 26 +-- .../PFQueryTableViewController.m | 30 +-- .../PFSignUpViewController.m | 4 +- 7 files changed, 245 insertions(+), 108 deletions(-) diff --git a/ParseUI/Classes/Internal/Extensions/PFUIAlertView.h b/ParseUI/Classes/Internal/Extensions/PFUIAlertView.h index 3478c95..f6742b3 100644 --- a/ParseUI/Classes/Internal/Extensions/PFUIAlertView.h +++ b/ParseUI/Classes/Internal/Extensions/PFUIAlertView.h @@ -21,14 +21,44 @@ #import -@interface PFUIAlertView : UIAlertView +NS_ASSUME_NONNULL_BEGIN -+ (void)showAlertViewWithTitle:(NSString *)title - error:(NSError *)error; -+ (void)showAlertViewWithTitle:(NSString *)title - message:(NSString *)message; -+ (void)showAlertViewWithTitle:(NSString *)title - message:(NSString *)message - cancelButtonTitle:(NSString *)cancelButtonTitle; +typedef void(^PFUIAlertViewCompletion)(NSUInteger selectedOtherButtonIndex); +typedef void(^PFUIAlertViewTextFieldCompletion)(UITextField *textField, NSUInteger selectedOtherButtonIndex); +typedef void(^PFUIAlertViewTextFieldCustomizationHandler)(UITextField *textField); + +@interface PFUIAlertView : NSObject + +///-------------------------------------- +#pragma mark - Present +///-------------------------------------- + ++ (void)presentAlertInViewController:(UIViewController *)viewController + withTitle:(NSString *)title + message:(nullable NSString *)message + cancelButtonTitle:(NSString *)cancelButtonTitle + otherButtonTitles:(nullable NSArray *)otherButtonTitles + completion:(nullable PFUIAlertViewCompletion)completion; + ++ (void)presentAlertInViewController:(UIViewController *)viewController + withTitle:(NSString *)title + message:(nullable NSString *)message + textFieldCustomizationHandler:(PFUIAlertViewTextFieldCustomizationHandler)textFieldCustomizationHandler + cancelButtonTitle:(NSString *)cancelButtonTitle + otherButtonTitles:(nullable NSArray *)otherButtonTitles + completion:(nullable PFUIAlertViewTextFieldCompletion)completion; + +///-------------------------------------- +#pragma mark - Convenience +///-------------------------------------- + ++ (void)presentAlertInViewController:(UIViewController *)viewController + withTitle:(NSString *)title + error:(NSError *)error; ++ (void)presentAlertInViewController:(UIViewController *)viewController + withTitle:(NSString *)title + message:(nullable NSString *)message; @end + +NS_ASSUME_NONNULL_END diff --git a/ParseUI/Classes/Internal/Extensions/PFUIAlertView.m b/ParseUI/Classes/Internal/Extensions/PFUIAlertView.m index b65b02e..155f70d 100644 --- a/ParseUI/Classes/Internal/Extensions/PFUIAlertView.m +++ b/ParseUI/Classes/Internal/Extensions/PFUIAlertView.m @@ -23,34 +23,191 @@ #import "PFLocalization.h" +@interface PFUIAlertView () + +@property (nonatomic, copy) PFUIAlertViewCompletion completion; + +@end + @implementation PFUIAlertView -+ (void)showAlertViewWithTitle:(NSString *)title error:(NSError *)error { +///-------------------------------------- +#pragma mark - Present +///-------------------------------------- + ++ (void)presentAlertInViewController:(UIViewController *)viewController + withTitle:(NSString *)title + message:(nullable NSString *)message + cancelButtonTitle:(NSString *)cancelButtonTitle + otherButtonTitles:(nullable NSArray *)otherButtonTitles + completion:(nullable PFUIAlertViewCompletion)completion { + if ([UIAlertController class] != nil) { + __block UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title + message:message + preferredStyle:UIAlertControllerStyleAlert]; + + void (^alertActionHandler)(UIAlertAction *) = [^(UIAlertAction *action) { + if (completion) { + // This block intentionally retains alertController, and releases it afterwards. + if (action.style == UIAlertActionStyleCancel) { + completion(NSNotFound); + } else { + NSUInteger index = [alertController.actions indexOfObject:action]; + completion(index - 1); + } + } + alertController = nil; + } copy]; + + [alertController addAction:[UIAlertAction actionWithTitle:cancelButtonTitle + style:UIAlertActionStyleCancel + handler:alertActionHandler]]; + + for (NSString *buttonTitle in otherButtonTitles) { + [alertController addAction:[UIAlertAction actionWithTitle:buttonTitle + style:UIAlertActionStyleDefault + handler:alertActionHandler]]; + } + + [viewController presentViewController:alertController animated:YES completion:nil]; + } else { +#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0 + __block PFUIAlertView *pfAlertView = [[self alloc] init]; + UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:title + message:message + delegate:nil + cancelButtonTitle:cancelButtonTitle + otherButtonTitles:nil]; + + for (NSString *buttonTitle in otherButtonTitles) { + [alertView addButtonWithTitle:buttonTitle]; + } + + pfAlertView.completion = ^(NSUInteger index) { + if (completion) { + completion(index); + } + + pfAlertView = nil; + }; + + alertView.delegate = pfAlertView; + [alertView show]; +#endif + } +} + ++ (void)presentAlertInViewController:(UIViewController *)viewController + withTitle:(NSString *)title + message:(nullable NSString *)message + textFieldCustomizationHandler:(PFUIAlertViewTextFieldCustomizationHandler)textFieldCustomizationHandler + cancelButtonTitle:(NSString *)cancelButtonTitle + otherButtonTitles:(nullable NSArray *)otherButtonTitles + completion:(nullable PFUIAlertViewTextFieldCompletion)completion { + if ([UIAlertController class] != nil) { + __block UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title + message:message + preferredStyle:UIAlertControllerStyleAlert]; + [alertController addTextFieldWithConfigurationHandler:textFieldCustomizationHandler]; + void (^alertActionHandler)(UIAlertAction *) = [^(UIAlertAction *action) { + if (completion) { + UITextField *textField = alertController.textFields.firstObject; + // This block intentionally retains alertController, and releases it afterwards. + if (action.style == UIAlertActionStyleCancel) { + completion(textField, NSNotFound); + } else { + NSUInteger index = [alertController.actions indexOfObject:action]; + completion(textField, index - 1); + } + } + alertController = nil; + } copy]; + + [alertController addAction:[UIAlertAction actionWithTitle:cancelButtonTitle + style:UIAlertActionStyleCancel + handler:alertActionHandler]]; + + for (NSString *buttonTitle in otherButtonTitles) { + [alertController addAction:[UIAlertAction actionWithTitle:buttonTitle + style:UIAlertActionStyleDefault + handler:alertActionHandler]]; + } + + [viewController presentViewController:alertController animated:YES completion:nil]; + } else { +#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0 + __block PFUIAlertView *pfAlertView = [[self alloc] init]; + UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:title + message:message + delegate:nil + cancelButtonTitle:cancelButtonTitle + otherButtonTitles:nil]; + alertView.alertViewStyle = UIAlertViewStylePlainTextInput; + for (NSString *buttonTitle in otherButtonTitles) { + [alertView addButtonWithTitle:buttonTitle]; + } + textFieldCustomizationHandler([alertView textFieldAtIndex:0]); + + __weak UIAlertView *walertView = alertView; + pfAlertView.completion = ^(NSUInteger index) { + if (completion) { + UITextField *textField = [walertView textFieldAtIndex:0]; + completion(textField, index); + } + + pfAlertView = nil; + }; + + alertView.delegate = pfAlertView; + [alertView show]; +#endif + } +} + +///-------------------------------------- +#pragma mark - Convenience +///-------------------------------------- + ++ (void)presentAlertInViewController:(UIViewController *)viewController + withTitle:(NSString *)title + error:(NSError *)error { NSString *message = error.userInfo[@"error"]; if (!message) { - message = [error.userInfo[@"originalError"] localizedDescription]; + message = [error.userInfo[@"originalError"] localizedDescription]; } if (!message) { - message = [error localizedDescription]; + message = [error localizedDescription]; } - [self showAlertViewWithTitle:title message:message]; + [self presentAlertInViewController:viewController withTitle:title message:message]; } -+ (void)showAlertViewWithTitle:(NSString *)title message:(NSString *)message { - [self showAlertViewWithTitle:title - message:message - cancelButtonTitle:PFLocalizedString(@"OK", @"OK")]; ++ (void)presentAlertInViewController:(UIViewController *)viewController + withTitle:(NSString *)title + message:(nullable NSString *)message { + [self presentAlertInViewController:viewController + withTitle:title + message:message + cancelButtonTitle:PFLocalizedString(@"OK", @"OK") + otherButtonTitles:nil + completion:nil]; } -+ (void)showAlertViewWithTitle:(NSString *)title - message:(NSString *)message - cancelButtonTitle:(NSString *)cancelButtonTitle { - UIAlertView *alertView = [[self alloc] initWithTitle:title - message:message - delegate:nil - cancelButtonTitle:cancelButtonTitle - otherButtonTitles:nil]; - [alertView show]; +#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0 + +///-------------------------------------- +#pragma mark - UIAlertViewDelegate +///-------------------------------------- + +- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { + if (self.completion) { + if (buttonIndex == alertView.cancelButtonIndex) { + self.completion(NSNotFound); + } else { + self.completion(buttonIndex - 1); + } + } } +#endif + @end diff --git a/ParseUI/Classes/LogInViewController/PFLogInViewController.m b/ParseUI/Classes/LogInViewController/PFLogInViewController.m index 6bf6a64..34e9d8b 100644 --- a/ParseUI/Classes/LogInViewController/PFLogInViewController.m +++ b/ParseUI/Classes/LogInViewController/PFLogInViewController.m @@ -235,17 +235,6 @@ - (BOOL)textFieldShouldReturn:(UITextField *)textField { return YES; } -///-------------------------------------- -#pragma mark - UIAlertViewDelegate -///-------------------------------------- - -- (void)alertView:(UIAlertView *)alertView willDismissWithButtonIndex:(NSInteger)buttonIndex { - if (buttonIndex != [alertView cancelButtonIndex]) { - NSString *email = [alertView textFieldAtIndex:0].text; - [self _requestPasswordResetWithEmail:email]; - } -} - ///-------------------------------------- #pragma mark - Private ///-------------------------------------- @@ -294,19 +283,22 @@ - (void)_forgotPasswordAction PF_EXTENSION_UNAVAILABLE("") { NSString *title = PFLocalizedString(@"Reset Password", @"Forgot password request title in PFLogInViewController"); NSString *message = PFLocalizedString(@"Please enter the email address for your account.", @"Email request message in PFLogInViewController"); - UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:title - message:message - delegate:self - cancelButtonTitle:PFLocalizedString(@"Cancel", @"Cancel") - otherButtonTitles:PFLocalizedString(@"OK", @"OK"), nil]; - alertView.alertViewStyle = UIAlertViewStylePlainTextInput; - - UITextField *textField = [alertView textFieldAtIndex:0]; - textField.placeholder = PFLocalizedString(@"Email", @"Email"); - textField.keyboardType = UIKeyboardTypeEmailAddress; - textField.returnKeyType = UIReturnKeyDone; - - [alertView show]; + [PFUIAlertView presentAlertInViewController:self + withTitle:title + message:message + textFieldCustomizationHandler:^(UITextField * _Nonnull textField) { + textField.placeholder = PFLocalizedString(@"Email", @"Email"); + textField.keyboardType = UIKeyboardTypeEmailAddress; + textField.returnKeyType = UIReturnKeyDone; + } + cancelButtonTitle:PFLocalizedString(@"Cancel", @"Cancel") + otherButtonTitles:@[ PFLocalizedString(@"OK", @"OK")] + completion:^(UITextField * _Nonnull textField, NSUInteger selectedOtherButtonIndex) { + if (selectedOtherButtonIndex != NSNotFound) { + NSString *email = textField.text; + [self _requestPasswordResetWithEmail:email]; + } + }]; } - (void)_requestPasswordResetWithEmail:(NSString *)email { @@ -316,13 +308,11 @@ - (void)_requestPasswordResetWithEmail:(NSString *)email { @"Password reset success alert title in PFLogInViewController."); NSString *message = [NSString stringWithFormat:PFLocalizedString(@"An email with reset instructions has been sent to '%@'.", @"Password reset message in PFLogInViewController"), email]; - [PFUIAlertView showAlertViewWithTitle:title - message:message - cancelButtonTitle:PFLocalizedString(@"OK", @"OK")]; + [PFUIAlertView presentAlertInViewController:self withTitle:title message:message]; } else { NSString *title = PFLocalizedString(@"Password Reset Failed", @"Password reset error alert title in PFLogInViewController."); - [PFUIAlertView showAlertViewWithTitle:title error:error]; + [PFUIAlertView presentAlertInViewController:self withTitle:title error:error]; } }]; } @@ -480,7 +470,7 @@ - (void)_loginDidFailWithError:(NSError *)error { } else { message = PFLocalizedString(@"Please try again", @"Generic login failed alert message in PFLogInViewController"); } - [PFUIAlertView showAlertViewWithTitle:title message:message]; + [PFUIAlertView presentAlertInViewController:self withTitle:title message:message]; } [[NSNotificationCenter defaultCenter] postNotificationName:PFLogInFailureNotification object:self]; } diff --git a/ParseUI/Classes/ProductTableViewController/PFProductTableViewController.m b/ParseUI/Classes/ProductTableViewController/PFProductTableViewController.m index 6b9b37a..544a38d 100644 --- a/ParseUI/Classes/ProductTableViewController/PFProductTableViewController.m +++ b/ParseUI/Classes/ProductTableViewController/PFProductTableViewController.m @@ -88,15 +88,15 @@ - (void)objectsDidLoad:(NSError *)error { cell.state = PFPurchaseTableViewCellStateDownloading; [PFPurchase downloadAssetForTransaction:transaction - completion:^(NSString *filePath, NSError *downloadError) { - if (!downloadError) { + completion:^(NSString *filePath, NSError *error) { + if (!error) { cell.state = PFPurchaseTableViewCellStateDownloaded; } else { cell.state = PFPurchaseTableViewCellStateNormal; NSString *title = PFLocalizedString(@"Download Error", @"Download Error"); - [PFUIAlertView showAlertViewWithTitle:title error:downloadError]; + [PFUIAlertView presentAlertInViewController:self withTitle:title error:error]; } } progress:^(int percentDone) { @@ -175,7 +175,7 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath [PFPurchase buyProduct:product.productIdentifier block:^(NSError *error) { if (error) { NSString *title = PFLocalizedString(@"Purchase Error", @"Purchase Error"); - [PFUIAlertView showAlertViewWithTitle:title error:error]; + [PFUIAlertView presentAlertInViewController:self withTitle:title error:error]; } }]; } diff --git a/ParseUI/Classes/QueryCollectionViewController/PFQueryCollectionViewController.m b/ParseUI/Classes/QueryCollectionViewController/PFQueryCollectionViewController.m index 9716289..6df7599 100644 --- a/ParseUI/Classes/QueryCollectionViewController/PFQueryCollectionViewController.m +++ b/ParseUI/Classes/QueryCollectionViewController/PFQueryCollectionViewController.m @@ -31,6 +31,7 @@ #import "PFImageView.h" #import "PFLoadingView.h" #import "PFLocalization.h" +#import "PFUIAlertView.h" static NSString *const PFQueryCollectionViewCellIdentifier = @"cell"; static NSString *const PFQueryCollectionViewNextPageReusableViewIdentifier = @"nextPageView"; @@ -391,31 +392,10 @@ - (void)_handleDeletionError:(NSError *)error { // Fully reload on error. [self loadObjects]; - NSString *errorMessage = [NSString stringWithFormat:@"%@: \"%@\"", + NSString *message = [NSString stringWithFormat:@"%@: \"%@\"", PFLocalizedString(@"Error occurred during deletion", @"Error occurred during deletion"), error.localizedDescription]; - - if ([UIAlertController class]) { - UIAlertController *errorController = [UIAlertController alertControllerWithTitle:PFLocalizedString(@"Error", @"Error") - message:errorMessage - preferredStyle:UIAlertControllerStyleAlert]; - - [errorController addAction:[UIAlertAction actionWithTitle:PFLocalizedString(@"OK", @"OK") - style:UIAlertActionStyleCancel - handler:nil]]; - - [self presentViewController:errorController animated:YES completion:nil]; - } else { - // Cast to `id` is required for building succesfully for app extensions, - // this code actually never runs in App Extensions, since they are iOS 8.0+, so we are good with just a hack - UIAlertView *alertView = [(id)[UIAlertView alloc] initWithTitle:PFLocalizedString(@"Error", @"Error") - message:errorMessage - delegate:nil - cancelButtonTitle:PFLocalizedString(@"OK", @"OK") - otherButtonTitles:nil]; - - [alertView show]; - } + [PFUIAlertView presentAlertInViewController:self withTitle:PFLocalizedString(@"Delete Error", @"Delete Error") message:message]; } #pragma mark - diff --git a/ParseUI/Classes/QueryTableViewController/PFQueryTableViewController.m b/ParseUI/Classes/QueryTableViewController/PFQueryTableViewController.m index e5bbf46..bf7a0d0 100644 --- a/ParseUI/Classes/QueryTableViewController/PFQueryTableViewController.m +++ b/ParseUI/Classes/QueryTableViewController/PFQueryTableViewController.m @@ -32,6 +32,7 @@ #import "PFLoadingView.h" #import "PFLocalization.h" #import "PFTableViewCell.h" +#import "PFUIAlertView.h" // Add headers to kill any warnings. // `initWithStyle:` is a UITableViewController method. @@ -521,31 +522,10 @@ - (void)_handleDeletionError:(NSError *)error { // Fully reload on error. [self loadObjects]; - NSString *errorMessage = [NSString stringWithFormat:@"%@: \"%@\"", - PFLocalizedString(@"Error occurred during deletion", @"Error occurred during deletion"), - error.localizedDescription]; - - if ([UIAlertController class]) { - UIAlertController *errorController = [UIAlertController alertControllerWithTitle:PFLocalizedString(@"Error", @"Error") - message:errorMessage - preferredStyle:UIAlertControllerStyleAlert]; - - [errorController addAction:[UIAlertAction actionWithTitle:PFLocalizedString(@"OK", @"OK") - style:UIAlertActionStyleCancel - handler:nil]]; - - [self presentViewController:errorController animated:YES completion:nil]; - } else { - // Cast to `id` is required for building succesfully for app extensions, - // this code actually never runs in App Extensions, since they are iOS 8.0+, so we are good with just a hack - UIAlertView *alertView = [(id)[UIAlertView alloc] initWithTitle:PFLocalizedString(@"Error", @"Error") - message:errorMessage - delegate:nil - cancelButtonTitle:PFLocalizedString(@"OK", @"OK") - otherButtonTitles:nil]; - - [alertView show]; - } + NSString *message = [NSString stringWithFormat:@"%@: \"%@\"", + PFLocalizedString(@"Error occurred during deletion", @"Error occurred during deletion"), + error.localizedDescription]; + [PFUIAlertView presentAlertInViewController:self withTitle:PFLocalizedString(@"Delete Error", @"Delete Error") message:message]; } #pragma mark - diff --git a/ParseUI/Classes/SignUpViewController/PFSignUpViewController.m b/ParseUI/Classes/SignUpViewController/PFSignUpViewController.m index 053583a..34c77e0 100644 --- a/ParseUI/Classes/SignUpViewController/PFSignUpViewController.m +++ b/ParseUI/Classes/SignUpViewController/PFSignUpViewController.m @@ -362,7 +362,7 @@ - (void)_signUpDidFailWithError:(NSError *)error { } if (message != nil) { - [PFUIAlertView showAlertViewWithTitle:title message:message]; + [PFUIAlertView presentAlertInViewController:self withTitle:title message:message]; [responder becomeFirstResponder]; return; @@ -370,7 +370,7 @@ - (void)_signUpDidFailWithError:(NSError *)error { } // Show the generic error alert, as no custom cases matched before - [PFUIAlertView showAlertViewWithTitle:title error:error]; + [PFUIAlertView presentAlertInViewController:self withTitle:title error:error]; } - (void)_cancelSignUp {