LoginSignup
8
7

More than 5 years have passed since last update.

UITextViewのプレースホルダを追加したい

Last updated at Posted at 2016-11-29

概要

62f6bac6-b659-11e6-851a-39f199a063f1.png

UITextFieldはプレースホルダをつけられるけど、UITextViewはつけられないので

完成品

//
//  PlaceHolderedTextView.swift
//  PlaceHolderedTextView
//
//  Created by はるふ on 2016/11/29.
//  Copyright © 2016年 はるふ. All rights reserved.
//

import UIKit

@IBDesignable class PlaceHolderedTextView: UITextView {
    @IBInspectable var placeHolder: String = ""
    @IBInspectable var placeHolderColor: UIColor = .lightGray

    private var placeHolderLayer: CATextLayer?

    private func createPlaceHolderLayerIfNeed() {
        if placeHolderLayer == nil {
            let layer = CATextLayer()
            layer.fontSize = self.font?.pointSize ?? UIFont.systemFontSize
            layer.frame = CGRect(x: self.textContainerInset.left + self.textContainer.lineFragmentPadding, y: self.textContainerInset.top, width: self.frame.width, height: layer.fontSize+10)
            layer.string = self.placeHolder
            layer.foregroundColor = placeHolderColor.cgColor
            layer.contentsScale = UIScreen.main.scale
            layer.alignmentMode = kCAAlignmentLeft
            self.layer.addSublayer(layer)
            placeHolderLayer = layer
        }
    }

    private func removePlaceHolderLayerIfNeed() {
        placeHolderLayer?.removeFromSuperlayer()
        placeHolderLayer = nil
    }

    private func updateLayer() {
        // Observerから呼ばれるとmainじゃないかも?
        DispatchQueue.main.async {
            if self.text == nil || self.text.isEmpty {
                self.createPlaceHolderLayerIfNeed()
            } else {
                self.removePlaceHolderLayerIfNeed()
            }
        }
    }

    func onChangedText(_ notification: NSNotification?) {
        updateLayer()
    }

    // MARK: Observer関連

    private func addObserver() {
        updateLayer()
        NotificationCenter.default.addObserver(self, selector: #selector(self.onChangedText(_:)), name: NSNotification.Name.UITextViewTextDidChange, object: self)
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        addObserver()
    }

    override init(frame: CGRect, textContainer: NSTextContainer?) {
        super.init(frame: frame, textContainer: textContainer)
        addObserver()
    }

    deinit {
        NotificationCenter.default.removeObserver(self)
    }
}

設定方法

  1. 新規ファイルを作成し、上のコードをコピペ
  2. StoryboardにUITextViewを配置する
  3. UITextViewを選択し、クラスを"PlaceHolderedTextView"に設定

以上でStoryboardから編集できるようになります

目指した理想形

探すとすぐに記事が出てきて、だいたい同じようなコードがあるが、
以下の点を鑑み、自分で作ろうと思った。

コードはViewに完結させたい

ViewControllerからいじるほどでもないし、使い回しも考えると、Viewのコードだけで実現できる方が良い

Observerを使いたくなかった(実現できなかった)

実現できなかったが・・・

override var text: String! {
    didSet {
        // ここでupdate
    } 
}

できるかと思ったら、できなかった。微妙なタイミングでしか呼ばれない。

delegateを使うと、ViewController側で使えなくなるので、使わなかった

うまく取れる方法があれば、教えていただきたい。

UILabelまでいらない

わざわざUILabelほどリッチなものはいらないと思った。
極論、一つのViewに完結したコードなので、CoreTextでも良い。

今回はCATextLayerを使いました。

drawでupdateされるコードが多い

これが結構よくわからなかったが、何故かdrawで更新するコードが多い。

func draw(_ rect: CGRect) {
}

でUILabelとかCALayerとかに変更を加えるのは、目的に合ってない。ここは、CoreTextなどを使った場合に描画する所(だと思う)

@IBDesignable, @IBInspectable

Storyboardからプレースホルダの色・文字を選択できる方が良い

表示だけなら、ビルドした後は反映されるっぽい
(triggerとか設定しなくて良いっぽい)

プレースホルダの位置

self.textContainer.lineFragmentPadding (textViewの両端にある5pxぐらいのpadding)
self.textContainerInset(textViewの上下にある8pxぐらいのpadding)

を考慮する必要がある。

でも、これでも何故か下に1, 2pxぐらいずれている気がします・・・CATextLayerにpaddingがあるのかな?と思いますが、詳しい方いれば教えていただきたいです。

おまけ:編集開始したら消す

色の設定によると思いますが、編集開始時にプレースホルダを消したい場合は updateLayer() を以下のように書き換えます

private func updateLayer() {
    // Observerから呼ばれるとmainじゃないかも?
    DispatchQueue.main.async {
        if (self.text == nil || self.text.isEmpty) && !self.isFirstResponder {
            self.createPlaceHolderLayerIfNeed()
        } else {
            self.removePlaceHolderLayerIfNeed()
        }
    }
}
8
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
7