ごんれのラボ

iOS、Android、Adobe系ソフトの自動化スクリプトのことを書き連ねています。

UIAlertController の UIAlertAction 実行時に、UIAlertView っぽくデリゲートメソッドを呼び出す方法

概要

UIAlertView を UIAlertController に置き換えることが決まって(いまさらとか言わないで…)、いろいろと検討した結果、UIAlertView のデリゲートメソッド内の処理をそのままか、ほぼ手を入れずにいきたいという要望があったため、UIAlertView っぽく UIAlertController の UIAlertAction 実行時にデリゲートメソッドを呼び出す実装を考えてみた。

※Qiitaに投稿した内容を転記して、一部はてなブログ用に改変 qiita.com

環境

  • MacOS 10.12.6
  • Xcode 9.1

想定

UIAlertView にタグがついてて、デリゲートメソッド内でどのボタンが押されたかを判別して処理を切り分けているよくある感じのコードの UIAlertController への置き換えを想定。

仕様

  • UIAlertView に設定していた tag を UIAlertController に付け替えできるように、UIAlertController に tag を保持できるようにしている
  • UIAlertView に設定していた delegate を UIAlertController に付け替えできるように、UIAlertController に delegate を保持できるようにしている
    • delegate 用に protocol を定義
  • UIAlertView のどのボタンがクリックされたという情報を付け替えるために UIAlertAction を追加する際に index を渡すようにして、UIAlertAction 実行時に index を取得できるようにしている

ソースコード

Gist にアップしました。
https://gist.github.com/macneko-ayu/3bcd1694c47c150ea0e36de4b8a18da4

Swift と Objective-C が混在しているという要件があるので、両方から使えるようにした。 Swift のみでの使用なら、tag と delegate は Computed Property で問題なし。

UIAlertController の Extension

UIAlertControllerExtension.swift

import UIKit

@objc protocol AlertControllerDelegate: class {
    func alertController(_ alert: UIAlertController, tappedIndex: Int) -> Void
}

private var DelegateKey: UInt8 = 0
private var TagKey: UInt8 = 0

extension UIAlertController {
    func setDelegate(_ delegate: AlertControllerDelegate?) {
        objc_setAssociatedObject(self, &DelegateKey, delegate, .OBJC_ASSOCIATION_RETAIN)
    }
    
    func getDelegate() -> AlertControllerDelegate? {
        guard let object = objc_getAssociatedObject(self, &DelegateKey) as? AlertControllerDelegate else {
            return nil
        }
        return object
    }
    
    func setTag(_ tag: Int) {
        objc_setAssociatedObject(self, &TagKey, tag, .OBJC_ASSOCIATION_RETAIN)
    }
    
    func getTag() -> Int {
        guard let object = objc_getAssociatedObject(self, &TagKey) as? Int else {
            return 0
        }
        return object
    }
    
    func addAction(title: String, style: UIAlertActionStyle = .default, index: Int) {
        addAction(UIAlertAction(title: title, style: style, handler: { (action) in
            self.getDelegate()?.alertController(self, tappedIndex: index)
            self.setDelegate(nil)
        }))
    }
}

Objective-C から使う場合

ViewController.m

@interface ViewController () <AlertControllerDelegate> 
@end

@implementation ViewController 
    
- (void)viewDidLoad {
    [super viewDidLoad];
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"title" message:@"message" preferredStyle:UIAlertControllerStyleAlert];
    [alert setDelegate:self];
    [alert setTag:3];
    [alert addActionWithTitle:@"OK" style:UIAlertActionStyleDefault index:5];
    [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:alert animated:YES completion:nil];
}

- (void)alertController:(UIAlertController * _Nonnull)alert tappedIndex:(NSInteger)tappedIndex {
    if ([alert getTag] == 3 && tappedIndex == 5) {
        NSLog(@"fuga");
    }
}

@end

Swift から使う場合

ViewController.swift

class ViewController: UIViewController, AlertControllerDelegate {
    override func viewDidLoad() {
        super.viewDidLoad()
        let alert = UIAlertController(title: "title", message: "message", preferredStyle: .alert)
        alert.setDelegate(self)
        alert.setTag(5)
        alert.addAction(title: "OK", index: 3)
        present(alert, animated: true, completion: nil)
    }
    
    func alertController(_ alert: UIAlertController, tappedIndex: Int) {
        if alert.getTag() == 5 && tappedIndex == 3 {
            print("hoge")
        }
    }
}

使い方

ソースコード参照。

まとめ

黒魔術感が強すぎて、うーん…。