我的应用程序使用UITextView。现在我想让UITextView有一个占位符,类似于你可以为UITextField设置的占位符。
如何做到这一点?
我的应用程序使用UITextView。现在我想让UITextView有一个占位符,类似于你可以为UITextField设置的占位符。
如何做到这一点?
当前回答
我做了我自己版本的UITextView的子类。我喜欢Sam Soffes使用通知的想法,但我不喜欢drawRect: overwrite。对我来说太过分了。我认为我做了一个非常干净的实现。
你可以看看我的子类。还包括一个演示项目。
其他回答
我通读了所有这些,但提出了一个非常简短的Swift 3解决方案,在我的所有测试中都有效。它可以更一般化一点,但过程很简单。这是整个东西,我称之为“TextViewWithPlaceholder”。
import UIKit
class TextViewWithPlaceholder: UITextView {
public var placeholder: String?
public var placeholderColor = UIColor.lightGray
private var placeholderLabel: UILabel?
// Set up notification listener when created from a XIB or storyboard.
// You can also set up init() functions if you plan on creating
// these programmatically.
override func awakeFromNib() {
super.awakeFromNib()
NotificationCenter.default.addObserver(self,
selector: #selector(TextViewWithPlaceholder.textDidChangeHandler(notification:)),
name: .UITextViewTextDidChange,
object: self)
placeholderLabel = UILabel()
placeholderLabel?.alpha = 0.85
placeholderLabel?.textColor = placeholderColor
}
// By using layoutSubviews, you can size and position the placeholder
// more accurately. I chose to hard-code the size of the placeholder
// but you can combine this with other techniques shown in previous replies.
override func layoutSubviews() {
super.layoutSubviews()
placeholderLabel?.textColor = placeholderColor
placeholderLabel?.text = placeholder
placeholderLabel?.frame = CGRect(x: 6, y: 4, width: self.bounds.size.width-16, height: 24)
if text.isEmpty {
addSubview(placeholderLabel!)
bringSubview(toFront: placeholderLabel!)
} else {
placeholderLabel?.removeFromSuperview()
}
}
// Whenever the text changes, just trigger a new layout pass.
func textDidChangeHandler(notification: Notification) {
layoutSubviews()
}
}
在看完所有答案后,我意识到我需要一些简单的,同时可重用的东西,这样我就可以在我的项目中为所有uitextview添加相同的功能。最后我得到了以下代码:
extension UITextView {
// MARK: TextView PlaceHolderLabel Setup
func createPlaceHolderLabel(with text: String) {
let lbl = UILabel()
self.addSubview(lbl)
// Add your constraints here
lbl.text = text
lbl.textColor = .lightGray
}
// My Textview contains only one UILabel, and for my use case the below code works, tweak it according to your use case
// Lastly two methods to toggle between show and hide the placeholder label
func hidePlaceHolderLabel() {
guard let lbl = self.subviews.first(where: { $0 is UILabel }) else { return }
lbl.isHidden = true
}
func showPlaceHolderLabel() {
guard let lbl = self.subviews.first(where: { $0 is UILabel }) else { return }
lbl.isHidden = false
}
}
当UILabel作为子视图添加到UITextView时,textView游标位置和UILabel位置将不匹配,因此在占位符文本之前留下一个空间
class ViewController: UIViewController, UITextViewDelegate {
var textView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(textView)
// Also setup textView constraints as per your need
// Add placeholder to your textView
// Leave one space before placeholder string
textView.createPlaceHolderLabel(with: " Address")
textView.delegate = self
}
然后在textViewdidChange方法中添加以下代码
func textViewDidChange(_ textView: UITextView) {
if textView.text.isEmpty {
textView.showPlaceHolderLabel()
} else {
textView.hidePlaceHolderLabel()
}
}
它可以在所有uitextview中重用。
TextView占位符
import UIKit
@IBDesignable
open class KMPlaceholderTextView: UITextView {
private struct Constants {
static let defaultiOSPlaceholderColor = UIColor(red: 0.0, green: 0.0, blue: 0.0980392, alpha: 0.22)
}
public let placeholderLabel: UILabel = UILabel()
private var placeholderLabelConstraints = [NSLayoutConstraint]()
@IBInspectable open var placeholder: String = "" {
didSet {
placeholderLabel.text = placeholder
}
}
@IBInspectable open var placeholderColor: UIColor = KMPlaceholderTextView.Constants.defaultiOSPlaceholderColor {
didSet {
placeholderLabel.textColor = placeholderColor
}
}
override open var font: UIFont! {
didSet {
if placeholderFont == nil {
placeholderLabel.font = font
}
}
}
open var placeholderFont: UIFont? {
didSet {
let font = (placeholderFont != nil) ? placeholderFont : self.font
placeholderLabel.font = font
}
}
override open var textAlignment: NSTextAlignment {
didSet {
placeholderLabel.textAlignment = textAlignment
}
}
override open var text: String! {
didSet {
textDidChange()
}
}
override open var attributedText: NSAttributedString! {
didSet {
textDidChange()
}
}
override open var textContainerInset: UIEdgeInsets {
didSet {
updateConstraintsForPlaceholderLabel()
}
}
override public init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
commonInit()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
#if swift(>=4.2)
let notificationName = UITextView.textDidChangeNotification
#else
let notificationName = NSNotification.Name.UITextView.textDidChangeNotification
#endif
NotificationCenter.default.addObserver(self,
selector: #selector(textDidChange),
name: notificationName,
object: nil)
placeholderLabel.font = font
placeholderLabel.textColor = placeholderColor
placeholderLabel.textAlignment = textAlignment
placeholderLabel.text = placeholder
placeholderLabel.numberOfLines = 0
placeholderLabel.backgroundColor = UIColor.clear
placeholderLabel.translatesAutoresizingMaskIntoConstraints = false
addSubview(placeholderLabel)
updateConstraintsForPlaceholderLabel()
}
private func updateConstraintsForPlaceholderLabel() {
var newConstraints = NSLayoutConstraint.constraints(withVisualFormat: "H:|-(\(textContainerInset.left + textContainer.lineFragmentPadding))-[placeholder]",
options: [],
metrics: nil,
views: ["placeholder": placeholderLabel])
newConstraints += NSLayoutConstraint.constraints(withVisualFormat: "V:|-(\(textContainerInset.top))-[placeholder]",
options: [],
metrics: nil,
views: ["placeholder": placeholderLabel])
newConstraints.append(NSLayoutConstraint(
item: placeholderLabel,
attribute: .width,
relatedBy: .equal,
toItem: self,
attribute: .width,
multiplier: 1.0,
constant: -(textContainerInset.left + textContainerInset.right + textContainer.lineFragmentPadding * 2.0)
))
removeConstraints(placeholderLabelConstraints)
addConstraints(newConstraints)
placeholderLabelConstraints = newConstraints
}
@objc private func textDidChange() {
placeholderLabel.isHidden = !text.isEmpty
self.layoutIfNeeded()
}
open override func layoutSubviews() {
super.layoutSubviews()
placeholderLabel.preferredMaxLayoutWidth = textContainer.size.width - textContainer.lineFragmentPadding * 2.0
}
deinit {
#if swift(>=4.2)
let notificationName = UITextView.textDidChangeNotification
#else
let notificationName = NSNotification.Name.UITextView.textDidChangeNotification
#endif
NotificationCenter.default.removeObserver(self,
name: notificationName,
object: nil)
}
}
使用
简单的Swift 3解决方案
添加UITextViewDelegate到类中
设置你的textview .delegate = self
创建placeholderLabel和位置它在你的textview
现在动画placeholderLabel。alpha在textViewDidChange:
func textViewDidChange(_ textView: UITextView) {
let newAlpha: CGFloat = textView.text.isEmpty ? 1 : 0
if placeholderLabel.alpha != newAlpha {
UIView.animate(withDuration: 0.3) {
self.placeholderLabel.alpha = newAlpha
}
}
}
你可能不得不使用placeholderLabel位置来设置它,但这应该不会太难
另一个解决方案
import UIKit
protocol PlaceholderTextViewDelegate: class {
func placeholderTextViewDidChangeText(_ text: String)
func placeholderTextViewDidEndEditing(_ text: String)
}
final class PlaceholderTextView: UITextView {
weak var notifier: PlaceholderTextViewDelegate?
var ignoreEnterAction: Bool = true
var placeholder: String? {
didSet {
text = placeholder
selectedRange = NSRange(location: 0, length: 0)
}
}
var placeholderColor = UIColor.lightGray {
didSet {
if text == placeholder {
textColor = placeholderColor
}
}
}
var normalTextColor = UIColor.lightGray
var placeholderFont = UIFont.sfProRegular(28)
fileprivate var placeholderLabel: UILabel?
// MARK: - LifeCycle
override var text: String? {
didSet {
if text == placeholder {
textColor = placeholderColor
} else {
textColor = normalTextColor
}
}
}
init() {
super.init(frame: CGRect.zero, textContainer: nil)
awakeFromNib()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func awakeFromNib() {
super.awakeFromNib()
self.delegate = self
}
}
extension PlaceholderTextView: UITextViewDelegate {
// MARK: - UITextViewDelegate
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if text == "" && textView.text == placeholder {
return false
}
if let placeholder = placeholder,
textView.text == placeholder,
range.location <= placeholder.count {
textView.text = ""
}
if ignoreEnterAction && text == "\n" {
textView.resignFirstResponder()
return false
}
return true
}
func textViewDidChange(_ textView: UITextView) {
if let placeholder = placeholder {
textView.text = textView.text.replacingOccurrences(of: placeholder, with: "")
}
if let placeholder = placeholder,
text?.isEmpty == true {
text = placeholder
textColor = placeholderColor
selectedRange = NSRange(location: 0, length: 0)
} else {
textColor = normalTextColor
}
notifier?.placeholderTextViewDidChangeText(textView.text)
}
func textViewDidChangeSelection(_ textView: UITextView) {
if let placeholder = placeholder,
text == placeholder {
selectedRange = NSRange(location: 0, length: 0)
}
}
func textViewDidEndEditing(_ textView: UITextView) {
notifier?.placeholderTextViewDidEndEditing(textView.text)
if let placeholder = placeholder,
text?.isEmpty == true {
text = placeholder
textColor = placeholderColor
selectedRange = NSRange(location: 0, length: 0)
} else {
textColor = normalTextColor
}
}
}
结果: