Я знаю, что на этот вопрос уже есть много ответов, но я не нашел ни одного из них достаточным (по крайней мере, в Swift). Мне нужна была функциональность "заполнитель" UITextField
в моем UITextView
(я хотел точное поведение, включая атрибуты отображения текста, анимацию и т. Д., И не хотел поддерживать это в течение долгого времени). Я также хотел решение, которое обеспечивало бы ту же самую точную границу, что и UITextField
(не приблизительную, которая выглядит примерно так, как выглядит прямо сейчас, а ту, которая выглядит точно так же и всегда будет выглядеть именно так). Поэтому, хотя я изначально не был фанатом включения дополнительного контроля в микс, казалось, что для достижения моих целей мне нужно было использовать фактический UITextField
и позволить ему выполнить свою работу.
Это решение обрабатывает размещение заполнителя и синхронизирует шрифт между двумя элементами управления, чтобы текст заполнителя был точным шрифтом и позицией в качестве текста, введенного в элемент управления (то, что многие другие решения не учитывают).
// This class is necessary to support "inset" (required to position placeholder
// appropriately in TextView)
//
class TextField: UITextField
{
var inset: UIEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0);
override func textRectForBounds(bounds: CGRect) -> CGRect
{
return UIEdgeInsetsInsetRect(bounds, inset);
}
override func placeholderRectForBounds(bounds: CGRect) -> CGRect
{
return UIEdgeInsetsInsetRect(bounds, inset);
}
}
// This class implements a UITextView that has a UITextField behind it, where the
// UITextField provides the border and the placeholder text functionality (so that the
// TextView looks and works like a UITextField).
//
class TextView : UITextView, UITextViewDelegate
{
var textField = TextField();
required init?(coder: NSCoder)
{
super.init(coder: coder);
}
override init(frame: CGRect, textContainer: NSTextContainer?)
{
super.init(frame: frame, textContainer: textContainer);
self.delegate = self;
// Create a background TextField with clear (invisible) text and disabled
self.textField.borderStyle = UITextBorderStyle.RoundedRect;
self.textField.textColor = UIColor.clearColor();
self.textField.userInteractionEnabled = false;
// Align the background TextView to where text appears in the TextField, so
// that any placeholder will be in the correct position.
self.textField.contentVerticalAlignment = UIControlContentVerticalAlignment.Top;
self.textField.inset = UIEdgeInsets(
top: self.textContainerInset.top,
left: self.textContainerInset.left + self.textContainer.lineFragmentPadding,
bottom: self.textContainerInset.bottom,
right: self.textContainerInset.right
);
// The background TextField should use the same font (for the placeholder)
self.textField.font = self.font;
self.addSubview(textField);
self.sendSubviewToBack(textField);
}
convenience init()
{
self.init(frame: CGRectZero, textContainer: nil)
}
override var font: UIFont?
{
didSet
{
// Keep the font of the TextView and background textField in sync
self.textField.font = self.font;
}
}
var placeholder: String? = nil
{
didSet
{
self.textField.placeholder = self.placeholder;
}
}
override func layoutSubviews()
{
super.layoutSubviews()
// Do not scroll the background textView
self.textField.frame = CGRectMake(0, self.contentOffset.y, self.frame.width, self.frame.height);
}
// UITextViewDelegate - Note: If you replace delegate, your delegate must call this
func scrollViewDidScroll(scrollView: UIScrollView)
{
// Do not scroll the background textView
self.textField.frame = CGRectMake(0, self.contentOffset.y, self.frame.width, self.frame.height);
}
// UITextViewDelegate - Note: If you replace delegate, your delegate must call this
func textViewDidChange(textView: UITextView)
{
// Updating the text in the background textView will cause the placeholder to
// appear/disappear (including any animations of that behavior - since the
// textView is doing this itself).
self.textField.text = self.text;
}
}