自作AlertViewを作ってみる

# はじめに
標準のUIAlertViewではなく、自分でカスタマイズしたAlertViewを表示したい時ってありますよね。ということで、実際に作ってみました。

# 方法１：UIViewを使って作る
UIViewに作成して、そこにLabelやButtonを設置する。
UIViewを保持しておいて、Buttonが押されたタイミングで親Viewから削除する。

```swift:UIView
    func myAlertShow() {
        let bound = self.view.bounds
        
        let myView = UIView.init(frame: CGRect.init(x: bound.width/2 - 200, y: bound.height/2 - 100, width: 400, height: 200))
        myView.layer.borderWidth = 3
        myView.layer.borderColor = UIColor.black.cgColor
        myView.layer.cornerRadius = 7
        myView.backgroundColor = .white
        
        let button = UIButton.init(frame: CGRect.init(x: 250, y: 130, width: 150, height: 70))
        button.backgroundColor = .gray
        button.layer.cornerRadius = 2.0;
        button.layer.borderColor = UIColor.black.cgColor
        button.layer.borderWidth = 1.0;
        button.addTarget(self, action: #selector(close), for: .touchUpInside)
        button.setTitle("OK", for: .normal)
        
        let label = UILabel.init(frame: CGRect.init(x: 10, y: 10, width: 400, height: 100))
        label.text = "myUIView"
        label.numberOfLines = 0
        myView.addSubview(button)
        myView.addSubview(label)
        
        self.alertView = myView
        self.view.addSubview(myView)
    }
    
    func close() {
        self.alertView?.removeFromSuperview()
    }
```
この方法だと当たり前ですが、AlertViewの後ろに控えるボタンなどをタップすること出来てしまいます。
UIAlertViewみたいな使い方がしたいなら、更に全体を覆うようなUIViewを追加するなどが必要です。


# 方法２：UIWindowを使って作る
UIWindowを使ってAlertViewを作成します。

``` swift:MyWindowAlert
protocol MyWindowAlertProtocol: class {
    func closeWindowAlert()
}

class MyWindowAlert: NSObject {
    var window: UIWindow?
    weak var alertProtocol: MyWindowAlertProtocol?
    
    init (with comment: String, delegate: MyWindowAlertProtocol) {
        super.init()
        self.alertProtocol = delegate
        
        let bound = UIScreen.main.bounds
        let myWindow  = UIWindow.init(frame: bound)
        myWindow.backgroundColor = UIColor.black.withAlphaComponent(0.2)
        
        let myView = UIView.init(frame: CGRect.init(x: bound.width/2 - 200, y: bound.height/2 - 100, width: 400, height: 200))

       (中略)

        label.numberOfLines = 0
        myView.addSubview(button)
        myView.addSubview(label)
        myWindow.addSubview(myView)
        
        self.window = myWindow
    }

    func show() {
        self.window?.makeKeyAndVisible()
    }
    
    func close() {
        self.window = nil
        self.alertProtocol?.closeWindowAlert()
    }
}
```
呼び出し元では。

```swift:ViewController
var alert: MyWindowAlert?
@IBAction func showWindowAlertButtonPushed(_ sender: Any) {
    self.alert = MyWindowAlert.init(with: "My Window Alert", delegate: self as MyWindowAlertProtocol)
    self.alert?.show()
}

extension ViewController: MyWindowAlertProtocol {
    func closeWindowAlert() {
        self.alert = nil
    }
}
```
先ほどと違って、AlertViewは表示された際に後ろに控えるボタンなどタップできなくなります。
ただし、UIWindowのインスタンスをどこかで保持しておかなければなりません。
例では、MyWindowAlertが保持し、その保持しているMyWindowAlertをViewControllerが保持しています。
なので削除する際には、delegateを使うなどでnilを代入します。
ちなみに、UIWindowはインスタンスにnilを入れることで解放されます。

保持して最後にnilを入れるって操作がなんか嫌だなと。

# 方法３：UIViewControllerを使って作る
UIAlertViewと同様にinitしてshowして終わりにしたい。というわけでこんな感じなりました。

```swift:MyAlertViewController
class MyViewControllerAlert: UIViewController {

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
    
    public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
    }
    
    convenience init() {
        self.init(nibName: nil, bundle: nil)
    }
    
    func setAlert(_ comment: String) {
        self.modalTransitionStyle = .crossDissolve
        self.modalPresentationStyle = .overCurrentContext
        self.view.backgroundColor = UIColor.black.withAlphaComponent(0.4)
       
        let bound = self.view.bounds
        
        let myView = UIView.init(frame: CGRect.init(x: bound.width/2 - 200, y: bound.height/2 - 100, width: 400, height: 200))
        
       (中略)

        label.numberOfLines = 0
        myView.addSubview(button)
        myView.addSubview(label)
        
        self.view.addSubview(myView)
    }
    
    func close() {
        for view in self.view.subviews {
            view.removeFromSuperview()
        }
        self.dismiss(animated: true, completion: nil)
    }
}
```
呼び出し元では、

```swift:ViewController
@IBAction func showAlertVCPushed(_ sender: Any) {
    let vc = MyViewControllerAlert()
    vc.setAlert("My ViewController Alert")
    self.present(vc, animated: true, completion: nil)
}
```
できました。initとして値セットしてpresentで終わり。
後ろに控えるボタンなどにも触れることもできません。


# まとめ
ViewControllerを使うことで、AlertViewっぽいものが作れました。
もっと良い方法などあれば、コメントいただけると嬉しいです。