iOSのさまざまなバージョン用のユニバーサルUIAlertControllerの作成

iOSバージョン8より前のUIKitで最も人気のあるクラスの1つは、UIAlertViewとUIActionSheetでした。 おそらく、アップルのモバイルプラットフォーム用アプリケーションの開発者は、遅かれ早かれ彼らに出くわしたでしょう。 メッセージまたはアクション選択メニューの表示は、ほとんどすべてのユーザーアプリケーションに不可欠な部分です。 これらのクラスを操作するため、またはボタンのクリックを処理するために、プログラマーはクラスに対応するデリゲートのメソッド-UIAlertViewDelegateまたはUIActionSheetDelegateを実装する必要がありました(上で何かが必要でない場合は、clickedButtonAtIndexメソッドを実装するだけで十分です)。 私の意見では、これは非常に不便です。異なるアクションのセットを持つ複数のダイアログボックスがオブジェクト内に作成された場合、それらは同じメソッドで処理され、条件の束が内部にあります。 iOSバージョン8のリリースにより、UIAlertControllerクラスがUIKitに登場し、UIAlertViewとUIActionSheetが置き換えられました。 そして、その主な特徴的な機能の1つは、デリゲートの代わりに、ブロックアプローチを使用することです。



UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Hello" message:@"Habr!" preferredStyle:UIAlertControllerStyleAlert]; [alertController addAction:[UIAlertAction actionWithTitle:@"Action" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { //    }]];
      
      





このアプローチにより、より構造化された論理コードを作成できます。 これ以降、プログラマはダイアログボックスの作成とイベントの処理を分離する必要がなくなりました-UIAlertControllerはこの誤解を排除しますが、同時にiOS 7以前のバージョンでは使用できないため、歴史的な不正をもたらします。 この問題を解決するにはいくつかの方法があります。





後者のオプションは最も論理的であり、ほとんどの開発者が選択すると確信していますが、この方法には重大な欠点があります。ダイアログボックスを表示する必要があるたびに、オペレーティングシステムのバージョンをチェックする条件を記述する必要があります。 実際にこれに直面して、特別なラッパークラスUIAlertDialogを作成しました。これにより、この問題を忘れることができます。



そのアイデアは、UIAlertControllerの便利なブロック構文を、iOSの最新バージョンに限定されないプロジェクトで使用できるということです。



ダイアログボックススタイルの定義



 typedef NS_ENUM(NSInteger, UIAlertDialogStyle) { UIAlertDialogStyleAlert = 0, UIAlertDialogStyleActionSheet };
      
      





ハンドラーブロックの種類



 typedef void(^UIAlertDialogHandler)(NSInteger buttonIndex);
      
      





クラス構造に移動できます:



 @interface UIAlertDialog : NSObject <UIAlertViewDelegate, UIActionSheetDelegate> - (instancetype)initWithStyle:(UIAlertDialogStyle)style title:(NSString *)title andMessage:(NSString *)message; - (void)addButtonWithTitle:(NSString *)title andHandler:(UIAlertDialogHandler)handler; - (void)showInViewController:(UIViewController *)viewContoller; @end
      
      





コンストラクター内
 - (instancetype)initWithStyle:(UIAlertDialogStyle)style title:(NSString *)title andMessage:(NSString *)message { if (self = [super init]) { self.style = style; self.title = title; self.message = message; self.items = [NSMutableArray new]; } return self; }
      
      







転送されたパラメーターは



内部変数
 @interface UIAlertDialog () @property (nonatomic) UIAlertDialogStyle style; @property (copy, nonatomic) NSString *title; @property (copy, nonatomic) NSString *message; @property (strong, nonatomic) NSMutableArray *items; @end
      
      







そして、ボタンのアクションを保存する配列が初期化されます(アイテム)。



新しいボタンを追加する:



 - (void)addButtonWithTitle:(NSString *)title andHandler:(UIAlertDialogHandler)handler { UIAlertDialogItem *item = [UIAlertDialogItem new]; item.title = title; item.handler = handler; [self.items addObject:item]; }
      
      





UIAlertDialogItemは



特別な内部クラス(UIAlertActionのアナログ)
 @interface UIAlertDialogItem : NSObject @property (copy, nonatomic) NSString *title; @property (copy, nonatomic) UIAlertDialogHandler handler; @end
      
      







ボタンのテキストとそれに関連付けられたアクションを保存します。



最後に、オペレーティングシステムのバージョンに応じてダイアログボックスの作成をカプセル化するshowInViewControllerメソッド:



 - (void)showInViewController:(UIViewController *)viewContoller { if ([[UIDevice currentDevice].systemVersion intValue] > 7) { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ [self showAlertControllerInViewController:viewContoller]; }]; return; } if (self.style == UIAlertDialogStyleActionSheet) { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ [self showActionSheetInView:viewContoller.view]; }]; } else { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ [self showAlert]; }]; } }
      
      





対応する各メソッドはすぐに実行されるのではなく、メインの実行キューに追加されることを強調します。 これは、ボタンハンドラで別のダイアログボックスが作成された場合、前のダイアログのアニメーションが完了した後にのみ表示されるためです。



ダイアログボックスを作成する方法を詳細に検討してください。



UIAlertController



 - (void)showAlertControllerInViewController:(UIViewController *)viewController { UIAlertController *alertController = [UIAlertController alertControllerWithTitle:self.title message:self.message preferredStyle:self.style == UIAlertDialogStyleActionSheet ? UIAlertControllerStyleActionSheet : UIAlertControllerStyleAlert]; NSInteger i = 0; for (UIAlertDialogItem *item in self.items) { UIAlertAction *alertAction = [UIAlertAction actionWithTitle:item.title style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { NSInteger buttonIndex = i; if (item.handler) { item.handler(buttonIndex); } }]; [alertController addAction:alertAction]; i++; } UIAlertAction *closeAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"close", nil) style:UIAlertActionStyleCancel handler:nil]; [alertController addAction:closeAction]; [viewController presentViewController:alertController animated:YES completion:nil]; }
      
      





このリストでは、行をマークしたいと思います



 NSInteger buttonIndex = i;
      
      





むしろ、コード内での位置。 作成されたコンテキストを保存するブロックのプロパティにより、押されたボタンのインデックスをブロックハンドラーに転送することが可能になります。 このメソッドは必須です。UIAlertActionには目的のパラメーターが含まれていません。



UIAlertViewおよびUIActionSheet



UIAlertDialogの説明によると、ダイアログボックスの作成は次のようになります。



 - (void)showMessage:(NSString *)message { UIAlertDialog *alertDialog = [[UIAlertDialog alloc] initWithStyle:UIAlertDialogStyleAlert title:message andMessage:nil]; [alertDialog showInViewController:self]; }
      
      





このクラスはUIAlertViewとUIActionSheetのデリゲートであるという事実のため



 @interface UIAlertDialog : NSObject <UIAlertViewDelegate, UIActionSheetDelegate>
      
      





1つのポイントを明確にする必要があります。



ご存じのように、クラスのデリゲートは、 weak修飾子を持つプロパティとして記述されます。 これは、デリゲートオブジェクトへの強い参照が存在しない場合、デリゲートメソッドを呼び出そうとするとEXC_BAD_ACCESS例外がスローされることを意味します。



私たちの場合、これはまさに起こることです-ARCは外部へのリンクがないため、 alertDialogを削除します。 この問題は、UIAlertViewおよびUIActionSheetの下位クラスを作成し、それらのダイアログオブジェクトへのリンクを追加することで解決できます。



 @interface UIAlertViewDialog : UIAlertView @property (strong, nonatomic) UIAlertDialog *alertDialog; @end
      
      





そして



 @interface UIActionSheetDialog : UIActionSheet @property (strong, nonatomic) UIAlertDialog *alertDialog; @end
      
      





操作のおかげで、ダイアログボックスを作成するためのコードは次の形式になります。



 - (void)showActionSheetInView:(UIView *)view { UIActionSheetDialog *actionSheet = [[UIActionSheetDialog alloc] initWithTitle:self.title delegate:self cancelButtonTitle:nil destructiveButtonTitle:nil otherButtonTitles:nil]; actionSheet.alertDialog = self; for (UIAlertDialogItem *item in self.items) { [actionSheet addButtonWithTitle:item.title]; } [actionSheet addButtonWithTitle:NSLocalizedString(@"close", nil)]; actionSheet.cancelButtonIndex = actionSheet.numberOfButtons - 1; [actionSheet showInView:view.window]; }
      
      





UIAlertViewに類似
 - (void)showAlert { UIAlertViewDialog *alertView = [[UIAlertViewDialog alloc] initWithTitle:self.title message:self.message delegate:self cancelButtonTitle:nil otherButtonTitles:nil]; alertView.alertDialog = self; for (UIAlertDialogItem *item in self.items) { [alertView addButtonWithTitle:item.title]; } [alertView addButtonWithTitle:NSLocalizedString(@"close", nil)]; alertView.cancelButtonIndex = alertView.numberOfButtons - 1; [alertView show]; }
      
      







最後のタッチ-ボタンのアクションの処理は、対応するデリゲートのメソッドで発生します。



 - (void)actionSheet:(UIActionSheetDialog *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex { if (buttonIndex == actionSheet.numberOfButtons - 1) { return; } UIAlertDialogItem *item = self.items[buttonIndex]; if (item.handler) { item.handler(buttonIndex); } }
      
      





UIAlertViewDelegate
 - (void)alertView:(UIAlertViewDialog *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { if (buttonIndex == alertView.numberOfButtons - 1) { return; } UIAlertDialogItem *item = self.items[buttonIndex]; if (item.handler) { item.handler(buttonIndex); } }
      
      







おわりに



その結果、ダイアログボックス( ソースコード )での作業に費やす時間を大幅に削減するシンプルでコンパクトなソリューションが得られます。



ご清聴ありがとうございました!



All Articles