[Swift 4.2] UITextViewにプレースホルダーを追加する[iOS 12]

##概要
UITextViewを継承してカスタムビューを作ります。

ちょっと古かったり、Notificationを使ったものはありましたが、
最新のSwift4.2対応でDelegateを使ったものが見つかりませんでしたので実装しました。

##クラス宣言
```swift
import UIKit

@IBDesignable class InspectableTextView: UITextView {
```
UITextViewを継承したカスタムクラスを作ります。
@IBDesignableをつけることによって、Storyboardに設定の変更がリアルタイムで反映されます。

ここでは「InspectableTextView」としましたが、クラス名は好きな名前をつけましょう。
~~DesignableTextViewの方がカッコよかったかな、と思っています。~~

##プレースホルダーに表示する文字列
```swift
    // MARK: - プロパティ
    /// プレースホルダーに表示する文字列（ローカライズ付き）
    @IBInspectable var localizedString: String = "" {
        didSet {
            guard !localizedString.isEmpty else { return }
            // Localizable.stringsを参照する
            placeholderLabel.text = NSLocalizedString(localizedString, comment: "")
            placeholderLabel.sizeToFit()  // 省略不可
        }
    }
```
@IBInspectableをつけることで、Storyboardの「Attributes Inspector」で直接値を編集できるようになります。
ここでは、NSLocalizedStringを使っているので、各国の翻訳ファイルを用意していれば翻訳された文字列が表示されます。
なくても入力した文字列がそのまま表示れるので問題はないです。

## プレースホルダー用ラベルを作成
```swift
    /// プレースホルダー用ラベルを作成
    private lazy var placeholderLabel = UILabel(frame: CGRect(x: 6, y: 6, width: 0, height: 0))
    
    /// プレースホルダーを設定する
    private func configurePlaceholder() {
        placeholderLabel.textColor = UIColor.gray
        addSubview(placeholderLabel)
    }
```
プレースホルダーの表示にはUILabelを使用します。
ここでは色をグレーとしました。
赤とかピンクでも個性的でクールかもしれません。

##プレースホルダーの表示・非表示切り替え
```swift
    /// プレースホルダーの表示・非表示切り替え
    private func togglePlaceholder() {
        // テキスト未入力の場合のみプレースホルダーを表示する
        placeholderLabel.isHidden = text.isEmpty ? false : true
    }
```
プレースホルダーは、テキストが入力されていない時のみ表示したいですよね。
ここでテキストが空か判定して表示の切り替えます。
非表示にしないとテキストと重なって見辛いです。

## デリゲートを自身に設定
```swift
    // MARK: - Viewライフサイクルメソッド
    /// ロード後に呼ばれる
    override func awakeFromNib() {
        super.awakeFromNib()
        delegate = self
        configurePlaceholder()
        togglePlaceholder()
    }
}
```
後述のデリゲートメソッドを使用するために、デリゲートを自身に設定しておきます。
プレースホルダーの設定メソッドと、表示切り替えメソッドもここで呼び出します。

物凄くざっくり言うと、viewDidLoadみたいな立ち位置でしょうか。

## TextViewのデリゲートメソッド
```swift
// MARK: -  UITextView Delegate
extension InspectableTextView: UITextViewDelegate {
    /// テキストが書き換えられるたびに呼ばれる ※privateにはできない
    func textViewDidChange(_ textView: UITextView) {
        togglePlaceholder()
    }
}
```
UITextViewDelegateプロトコルを使います。

`@IBDesignable class InspectableTextView: UITextView, UITextViewDelegate`
として、クラス内に書いてももちろんOKですが、
extensionを使うとプロトコルの実装部分を切り離せます。

見通しが良くなるので個人的にはこちらの方が好きです。

## 全文
```swift
import UIKit

@IBDesignable class InspectableTextView: UITextView {
    
    // MARK: - プロパティ
    /// プレースホルダーに表示する文字列（ローカライズ付き）
    @IBInspectable var localizedString: String = "" {
        didSet {
            guard !localizedString.isEmpty else { return }
            // Localizable.stringsを参照する
            placeholderLabel.text = NSLocalizedString(localizedString, comment: "")
            placeholderLabel.sizeToFit()  // 省略不可
        }
    }
    
    /// プレースホルダー用ラベルを作成
    private lazy var placeholderLabel = UILabel(frame: CGRect(x: 6, y: 6, width: 0, height: 0))
    
    // MARK: - Viewライフサイクルメソッド
    /// ロード後に呼ばれる
    override func awakeFromNib() {
        super.awakeFromNib()
        delegate = self
        configurePlaceholder()
        togglePlaceholder()
    }
    
    /// プレースホルダーを設定する
    private func configurePlaceholder() {
        placeholderLabel.textColor = UIColor.gray
        addSubview(placeholderLabel)
    }
    
    /// プレースホルダーの表示・非表示切り替え
    private func togglePlaceholder() {
        // テキスト未入力の場合のみプレースホルダーを表示する
        placeholderLabel.isHidden = text.isEmpty ? false : true
    }
}

// MARK: -  UITextView Delegate
extension InspectableTextView: UITextViewDelegate {
    /// テキストが書き換えられるたびに呼ばれる ※privateにはできない
    func textViewDidChange(_ textView: UITextView) {
        togglePlaceholder()
    }
}
```

##参考
https://qiita.com/Takeshi_Akutsu/items/66a24060fa026b4f41d1
https://re-engines.com/2018/05/07/swift4uitextview%E3%81%AB%E3%83%97%E3%83%AC%E3%83%BC%E3%82%B9%E3%83%9B%E3%83%AB%E3%83%80%E3%82%92%E3%81%A4%E3%81%91%E3%82%8B/
https://qiita.com/uhooi/items/40059a1d987b64fd1aa7