简体   繁体   中英

How to create a UITextView or a UITextField that has a placeHolder, multiline and vertically centered?

What I want is a very intuitive thing but surprisingly it is not included in Xcode: what I want is a textView or a textField that will have

1- a placeholder
2- several lines (with scroll view like in UITextView).
3- either the text or the placeholder should always be vertically and horizontally centered in the textBoxArea .

I've looked at similar questions like:
Center text vertically in a UITextView
Center the text in a UITextView, vertically and horizontally
Aligning text vertically center in UITextView

for the placeHolder I am using the following library:
https://github.com/devxoul/UITextView-Placeholder which solves the first requirement but I couldn't satisfy the other 2 requirements even with the answer given in the links above

I am using swift 3.1, deployment target iOS9, Xcode 8.3.2

It is unfortunate that both UITextView and UITextField are so limited. These same things have been done for so many times by so many developers.

What I usually do is just create a new UIView subclass which then has 2 subviews; A label for placeholder and centered text view for content.

You can do it in xib and use the interface builder and I suggest you to do so. You can then expose both text view and placeholder and you may actually set pretty much anything you would ever need.

But to do it in the code it would look something like the following:

@IBDesignable class CustomTextView: UIView {

    private(set) var textView: UITextView = UITextView(frame: CGRect.zero)
    private(set) var placeholderLabel: UILabel = UILabel(frame: CGRect.zero)

    @IBInspectable var placeholder: String? {
        didSet {
            placeholderLabel.text = placeholder
        }
    }

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

    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }

    private func setup() {
        textView.delegate = self
        addSubview(textView)
        textView.backgroundColor = UIColor.clear
        textView.textAlignment = .center

        addSubview(placeholderLabel)
        placeholderLabel.textAlignment = .center
        placeholderLabel.numberOfLines = 0

        addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(onTap)))
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        refreshViews()
    }

    fileprivate func refreshViews() {
        UIView.performWithoutAnimation {
            textView.frame = self.bounds

            let optimalSize = textView.sizeThatFits(bounds.size)
            textView.frame = CGRect(x: 0.0, y: max((bounds.size.height-optimalSize.height)*0.5, 0.0), width: bounds.width, height: min(optimalSize.height, bounds.size.height))
            placeholderLabel.frame = bounds
            placeholderLabel.backgroundColor = UIColor.clear

            if textView.text.isEmpty && textView.isFirstResponder == false {
                placeholderLabel.text = placeholder
                placeholderLabel.isHidden = false
                textView.isHidden = true
            } else {
                placeholderLabel.isHidden = true
                textView.isHidden = false
            }
        }
    }

    @objc private func onTap() {
        if !textView.isFirstResponder {
            textView.becomeFirstResponder()
        }
    }

}

extension CustomTextView: UITextViewDelegate {

    func textViewDidChange(_ textView: UITextView) {
        refreshViews()
    }

    func textViewDidBeginEditing(_ textView: UITextView) {
        refreshViews()
    }

    func textViewDidEndEditing(_ textView: UITextView) {
        refreshViews()
    }

}

There are still a few downsides though:

  1. The delegate for the text view is needed by the view itself. So if you would call myView.textView.delegate = self in some view controller you would break the logic inside. If you need it I suggest you create a custom delegate on this view and expose the methods you need instead of the standard UITextViewDelegate .
  2. Although the class is designable you can not have many inspectable fields. So it is a bit hard to set any properties inside the interface builder. You could expose properties such as "text" and put setter and getter to point to the text view. Could do the same for text color... But the biggest problem is you cant mark fonts as inspectable (cry to Apple I guess).
  3. In some cases text jumps a bit when going into newline. I guess you would need to play with it a bit to perfect it. It might be that constraints would fix this so if you make it in xib this could solve it.

As a good thing though you can do pretty much anything from the code. Multiline or attributed string placeholders, alignments, colors, fonts...

actually I feel a little silly because the answer was infront of my eye all the time ...

For the PlaceHolder I'll just use the library: https://github.com/devxoul/UITextView-Placeholder mentioned in my question

then in the UIViewController class I did the following:

class myClass: UIViewController {

      @IBOutlet weak var myTextView: UITextView!

      override func viewDidLoad() {
          super.viewDidLoad()
          // Set the textView to the Delegate 
          myTextView.delegate = self
          // Initialize the textView text Position and accordingly the
          //   placeholder position 
          myTextView.contentInset.top = max(0, (tv_comment.bounds.size.height - tv_comment.contentSize.height * tv_comment.zoomScale) / 2)

      }

       extension myClass: UITextViewDelegate {
              func textViewDidChange(_ textView: UITextView) {
                   // do the same adjustment again every time the textView changes
                   textView.contentInset.top = max(0, (textView.bounds.size.height - textView.contentSize.height * textView.zoomScale) / 2)
              }
       }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM