UITextView にプレースホルダーを設定できるようにする

検証環境:
Xcode 11.3
Swift 5.1.3

たまに必要になるのでメモ。

import UIKit

@IBDesignable
open class PlaceHolderTextView: UITextView, UITextViewDelegate {
    
    @IBInspectable
    open var placeHolderText: String = "" {
        didSet {
            placeHolderLabel.text = placeHolderText
        }
    }
    
    @IBInspectable
    open var margin: CGFloat = 0
    
    open var placeHolderLabel: UILabel = .init()
    
    open override func awakeFromNib() {
        super.awakeFromNib()
        configure()
    }

    open override func prepareForInterfaceBuilder() {
        super.prepareForInterfaceBuilder()
        configure()
    }

    // text プロパティに直接文字列をセットした場合、textViewDidChange は呼ばれないので override して update を呼ぶ
    open override var text: String! {
        didSet {
            update()
        }
    }

    private func configure() {
        placeHolderLabel.lineBreakMode = .byWordWrapping
        placeHolderLabel.numberOfLines = 0
        placeHolderLabel.font = font
        placeHolderLabel.textColor = .lightGray
        addSubview(placeHolderLabel)
        
        placeHolderLabel.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            placeHolderLabel.topAnchor.constraint(equalTo: topAnchor, constant: margin),
            placeHolderLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: margin),
            placeHolderLabel.widthAnchor.constraint(equalTo: widthAnchor, constant: -(margin * 2))
        ])
        
        update()
        delegate = self
    }
    
    private func update() {
        placeHolderLabel.isHidden = placeHolderText.isEmpty || !text.isEmpty
    }
    
    // MARK: - UITextViewDelegate
    public func textViewDidChange(_ textView: UITextView) {
        update()
    }
}