やりたいこと
- アラートダイアログを出したい。
- AlertControllerを使えば良いが、iOS7に対応させるためにはUIAlertViewを使う必要がある。
- しかし、使うたびに場合分けを書いていては面倒。
- 何よりコールバックとデリゲートで別々に処理を書かなきゃいけないなんて面倒な上コードがよくわからなくなる。
- そのためできるだけ同様に扱いたい。
ということで、両者の差異を吸収しつつ、同様に扱うメソッドを作成してみる。
たぶん既出だとは思います(それももっと良いやり方)が、勉強がてらやってみました。
2016/2/5追記
コントローラの継承なんかで実装するより、固有のクラスにいた方がいいよねってことで、ほぼ同じ処理を別のクラスに分け、viewControllerを引数にとるようにしました。以下のコードには反映していませんが、実装はほぼ同じです。いずれする。
コードと解説
ということで、次のように実装してみました。
AlertButton.swift
struct AlertButton {
var title: String
var action: (AnyObject) -> Void
init(title: String,action: (AnyObject) -> Void) {
self.title = title
self.action = action
}
}
BaseViewController.swift
class BaseViewController: UIViewController,UIAlertViewDelegate {
var doActionsInAlert: Dictionary<Int,Any> = [:]
/// アラート(ダイアログ)のOS差を吸収するメソッド。デリゲートを使わず全てコールバックで回収する。
/// - parameter title: アラートのタイトル
/// - parameter message: アラートの本文
/// - parameter cancelButton: キャンセルボタン用のAlertButtonオブジェクト。タイトルとコールバックをそれぞれ登録する。
/// - parameter otherButton: 追加ボタンの配列。中身はAlertButtonオブジェクトでタイトルとコールバックをそれぞれ登録する。
func showCustomAlert(title: String, message: String, tag:Int ,cancelButton: AlertButton, otherButtons: AlertButton...) {
if #available(iOS 8.0, *) {
//UIAlertController使用
let alertController = UIAlertController(title: title, message: message, preferredStyle: .Alert)
// キャンセルボタンを設定・登録
let cancelAction = UIAlertAction(title: cancelButton.title, style: .Cancel) { (action) -> Void in
// コールバックを呼び出す
let callback = cancelButton.action
callback(cancelButton.title)
}
alertController.addAction(cancelAction)
// それ以外のボタンを設定・登録
for button in otherButton {
let action = UIAlertAction(title: button.title , style: .Default) { (action) -> Void in
// コールバックを呼びだす
let callback = button.action
callback(button.title)
}
alertController.addAction(action)
}
presentViewController(alertController, animated: true, completion: nil)
} else {
//UIAlertView使用(iOS7の場合)
// 初期化用に最初のボタンのみ取り出す
let firstOtherButton: AlertButton = otherButton[otherButton.startIndex]
var alertIndex = 1
let alertView = UIAlertView(
title: title,
message: message,
delegate: self,
cancelButtonTitle: cancelButton.title,
otherButtonTitles: firstOtherButton.title
)
alertView.tag = tag
// デリゲートメソッドで呼び出されるメソッドを登録
var doMethods:Dictionary<Int,(AnyObject) -> Void> = [:]
doMethods[alertView.cancelButtonIndex] = cancelButton.action
doMethods[alertIndex++] = firstOtherButton.action
// 全てのボタンについてデリゲートメソッドで呼び出されるメソッドを登録
for (index, button) in otherButton.enumerate() {
if index == otherButton.startIndex {
continue
}
alertView.addButtonWithTitle(button.title)
doMethods[alertIndex++] = button.action
}
self.doActionsInAlert[tag] = doMethods
alertView.show()
}
}
func alertView(alertView: UIAlertView, clickedButtonAtIndex buttonIndex: Int) {
let actions = self.doActionsInAlert[alertView.tag] as! Dictionary<Int,(AnyObject) -> Void>
let doAction = actions[buttonIndex]
doAction!(alertView.title)
self.doActionsInAlert[alertView.tag] = nil
}
}
上記のクラスを継承したコントローラの中で次のように使う。
let cancelButton = AlertButton(
title: "キャンセル",
action: {response in
print(response)
}
)
let otherButton = AlertButton(
title: "ボタン1",
action: {response in
print(response)
}
)
self.showCustomAlert("test", message: "alertのテストです", tag:1, cancelButton: cancelButton, otherButtons: otherButton)
コード解説をすると、概要は以下のようになる。
- AlertButtonオブジェクトにはタイトルとコールバックメソッドを登録する。
- UIAlertControllerがあるかどうかで分岐。
- UIAlertControllerの場合はキャンセルボタンおよびその他のボタンのアクションを登録する。その中でコールバックを呼び出す。
- UIAlertViewの場合、キャンセルボタンおよびその他のボタンのアクションを登録しつつ、doActionsInAlertへコールバックメソッドを登録する。この際、複数のUIAlertViewが必要な場合を考慮し、tagでdoActionsInAlertに仕切りを作る。
- デリゲートメソッド上で押したボタンと対応したdoActionsInAlertのメソッドを呼び出す。
まとめ
以上のようにすることで、同様に扱うことができる。iOSのバージョンを気にせずできるやったね。
反省
- デリゲートメソッドをに任意の引数をうまく渡すいい方法はないものかな。プロパティで片付けてしまうのはスマートではないような気がする。
- ベースとなるControllerを作成するのではなく、extensionなど別の形で実装したかった。
- そもそもiOS7にはもう対応しなくていいのではという議論はある。正直なところしたくない。
- callbackで片付けるのとデリゲートで解決するのどっちがスマートなんだろう。通知を使うという方法もあったけれど、どうするのが良いのだろう。